你为什么要锁定线程?

Mis*_*ahX 8 python multithreading locking thread-safety

我已经阅读了很多关于锁定线程的例子..但为什么要锁定它们呢?根据我的理解,当您启动线程而不加入它们时,它们将与主线程和所有其他线程竞争资源,然后执行,有时同时执行,有时不执行.

锁定是否确保线程不会同时执行?

同时,线程执行同步有什么问题?那不是更好吗?(整体执行速度更快)

当您锁定线程时,它会锁定它们还是您可以选择要锁定的线程?(无论锁定实际上是什么......)

我指的是使用lock()这样的锁函数并在线程模块中获取btw ...

kin*_*all 14

锁允许您强制多个线程一次访问一个资源,而不是所有线程同时尝试访问资源.

如您所知,通常您确实希望线程同时执行.但是,假设您有两个线程,它们都写入同一个文件.如果他们试图同时写入同一个文件,他们的输出会混合在一起,并且这两个线程都不会真正成功地将文件放入他们想要的内容中.

现在也许这个问题不会一直出现.大多数情况下,线程不会尝试一次写入文件.但有时候,也许有一次在一千次跑步中,他们就会这样做.所以也许你有一个看似随机发生的bug,很难重现,因此难以修复.啊!

或许......这发生在我工作的公司......你有这样的错误,但不知道它们在那里,因为几乎没有任何客户有超过4个CPU.然后他们都开始购买16个CPU的盒子......你的软件运行的线程与CPU内核一样多,所以现在有4倍的线程,突然你崩溃了很多或者得到了错误的结果.

所以无论如何,回到文件.为了防止线程相互踩踏,每个线程必须在写入之前获取文件锁定.一次只有一个线程可以保持锁定,因此一次只能有一个线程写入该文件.线程保持锁定,直到完成写入文件,然后释放锁定,以便另一个线程可以使用该文件.

如果线程正在写入不同的文件,则不会出现此问题.这是一个解决方案:让您的线程写入不同的文件,并在必要时将它们合并.但这并不总是可行的; 有时候,只有一件事.

它不一定是文件.假设您试图简单地计算一堆不同文件中字母"A"的出现次数,每个文件一个线程.你认为,很明显,每次看到"A"时,我都会让所有线程增加相同的内存位置.但!当你去增加保持计数的变量时,计算机将变量读入寄存器,递增寄存器,然后将值存回.如果两个线程同时读取该值,同时递增该值并将其同时存储,该怎么办?他们都从10开始,将它增加到11,然后再存储11.所以柜台11应该是12:你丢了一个计数.

获取锁定可能很昂贵,因为您必须等到其他任何人正在使用该资源.这就是Python的Global Interpreter Lock是性能瓶颈的原因.因此,您可能决定完全避免使用共享资源.每个线程都保留自己的计数,而不是使用单个内存位置来保存文件中的"A"数,而是在最后添加它们(类似于我建议的文件解决方案,有趣的是) .

  • 你没有锁定线程; 线程*获取*或*版本*对某些共享资源的锁定.我想一个等待释放锁的线程可以说是"锁定"但这并不是常见的说法. (8认同)

und*_*run 9

首先,锁是为保护资源而设计的; 线程没有"锁定"或"解锁"它们/获取/锁定(在资源上)和/ release/a lock(在资源上).

你想让线程尽可能并发地运行是正确的,但让我们来看看:

y=10

def doStuff( x ):
    global y
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y

t1 = threading.Thread( target=doStuff, args=(8,) )
t2 = threading.Thread( target=doStuff, args=(8,) )
t1.start()
t2.start()
t1.join()
t2.join()
Run Code Online (Sandbox Code Playgroud)

现在,您可能知道这些线程中的任何一个都可以完成并首先打印.你会期望看到两个输出30.

但他们可能不会.

y是一个共享资源,在这种情况下,读取和写入y的位是所谓的"临界区"的一部分,应该由锁保护.原因是你没有得到工作单元:任何一个线程都可以随时获得CPU.

想想这样:

t1正在愉快地执行代码并且它会命中

a = 2 * y
Run Code Online (Sandbox Code Playgroud)

现在t1有一个= 20并且暂停执行一段时间.当t1等待更多CPU时间时,t2变为活动状态.t2执行:

a = 2 * y
b = y / 5
y = a + b + x
Run Code Online (Sandbox Code Playgroud)

此时全局变量y = 30

t2停止一点停止,t1再次启动.它执行:

b = y / 5
y = a + b + x
Run Code Online (Sandbox Code Playgroud)

由于设定b时y为30,因此b = 6,y现在设定为34.

打印的顺序也是非确定性的,你可能先得到30或者先得到34.

使用锁我们会:

global l
l = threading.Lock()
def doStuff( x ):
    global y
    global l
    l.acquire()
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y
    l.release()
Run Code Online (Sandbox Code Playgroud)

这必然使这段代码成为线性 - 一次只有一个线程.但是如果整个程序是顺序的,那么你不应该使用线程.我们的想法是,您可以根据可以执行外部锁定并且并行运行的代码百分比来提高速度.这是(一个原因)为什么在2核系统上使用线程不会使所有内容的性能加倍.

锁本身也是一个共享资源,但它必须是:一旦一个线程获得锁,所有其他尝试获取/ same/lock的线程将被阻塞,直到它被释放.一旦它被释放,第一个向前移动并获得锁定的线程将阻止所有其他等待线程.

希望这足以继续下去!