版权声明:要转随便转,如果能加上原文的链接就感谢各位了。( ⊙ o ⊙ ) https://blog.csdn.net/Hungryof/article/details/79804972
总说
前面一篇讲了如何开启多线程, 并且单独执行. 然而这些线程是不会互相干扰的, 但是在很多情况下, 线程之间的执行会互相干扰,这就需要引入同步. 同步指的是有一些代码块在某个时刻只能被一个线程执行! 比如修改数据库, 更新文件等等. 我们需要引入同步原语来进行操作这些代码(称为临界区代码).
一般有两种: 锁和信号量. 锁没啥好说的, 信号量针对多线程竞争有限资源的情况.
先看一个例子, 这里例子随机初始化3-6个线程, 每个线程运行2-4秒.
from atexit import register
from random import randrange
from threading import Thread, current_thread, Lock
from time import sleep, ctime
class CleanOutputSet(set):
def __str__(self):
return ','.join(x for x in self)
# randomly create 3-6 threads, each of which sleeps for 2-4 seconds.
loops = (randrange(2,5) for x in range(randrange(3,7)))
remaining = CleanOutputSet()
lock = Lock()
def loop(nsec):
myname = current_thread().name
# lock.acquire()
remaining.add(myname)
print('[{0}] Started {1}'.format(ctime(), myname))
# lock.release()
sleep(nsec)
# lock.acquire()
remaining.remove(myname)
print('{0} Completed {1} ({2} secs)'.format(ctime(), myname, nsec))
print(' (remaining: {0})'.format(remaining or 'NONE'))
# lock.release()
def main():
for pause in loops:
thread = Thread(target=loop, args=(pause,)).start()
@register
def _atexit():
print('all done at:', ctime())
if __name__ == '__main__':
main()
输出
[Tue Apr 3 15:53:01 2018] Started Thread-1
[Tue Apr 3 15:53:01 2018] Started Thread-2
[Tue Apr 3 15:53:01 2018] Started Thread-3
[Tue Apr 3 15:53:01 2018] Started Thread-4
Tue Apr 3 15:53:04 2018 Completed Thread-2 (3 secs)
(remaining: Thread-1,Thread-3,Thread-4)
Tue Apr 3 15:53:04 2018 Completed Thread-4 (3 secs)
(remaining: Thread-1,Thread-3)
Tue Apr 3 15:53:04 2018 Completed Thread-3 (3 secs)
Tue Apr 3 15:53:04 2018 Completed Thread-1 (3 secs)
(remaining: NONE)
(remaining: NONE)
all done at: Tue Apr 3 15:53:04 2018
可能会得到上面这种奇怪的输出, Thread-3
和Thread-1
都是运行3秒, 以至于 ‘Thread-3’的print(' (remaining: {0})'.format(remaining or 'NONE'))
还没开始运行, Thread-1
也结束了. 所以remaining
返回的是NONE
, 紧接着输出的是 Thread-1
的print(' (remaining: {0})'.format(remaining or 'NONE'))
. 因此会出现两个(remaining: NONE)
.
解决方案: 因为这里的输出都依赖于remaining
这个变量. 因此, 多个线程如果都会修改某一个变量, 那么在这个变量改变的操作前后加上锁操作! 即前面acquire(), 后面release().
将上面代码的注释去掉,我们就会得到正确的输出:
[Tue Apr 3 16:04:55 2018] Started Thread-1
[Tue Apr 3 16:04:55 2018] Started Thread-2
[Tue Apr 3 16:04:55 2018] Started Thread-3
[Tue Apr 3 16:04:55 2018] Started Thread-4
[Tue Apr 3 16:04:55 2018] Started Thread-5
[Tue Apr 3 16:04:55 2018] Started Thread-6
Tue Apr 3 16:04:57 2018 Completed Thread-4 (2 secs)
(remaining: Thread-1,Thread-2,Thread-5,Thread-3,Thread-6)
Tue Apr 3 16:04:57 2018 Completed Thread-1 (2 secs)
(remaining: Thread-2,Thread-5,Thread-3,Thread-6)
Tue Apr 3 16:04:57 2018 Completed Thread-5 (2 secs)
(remaining: Thread-2,Thread-3,Thread-6)
Tue Apr 3 16:04:57 2018 Completed Thread-2 (2 secs)
(remaining: Thread-3,Thread-6)
Tue Apr 3 16:04:58 2018 Completed Thread-6 (3 secs)
(remaining: Thread-3)
Tue Apr 3 16:04:58 2018 Completed Thread-3 (3 secs)
(remaining: NONE)
all done at: Tue Apr 3 16:04:58 2018
使用上下文管理器
太麻烦了, 前面acquire(), 后面release(). 另一种写法就是with
语句, 此时每个对象的上下文管理器负责在进入该套件之前调用acquire()
, 结束之后调用 release()
def loop(nsec):
myname = current_thread().name
with lock:
remaining.add(myname)
sleep(nsec)
with lock:
remaining.remove(myname)
print('{0} Completed {1} ({2} secs)'.format(ctime(), myname, nsec))
print(' (remaining: {0})'.format(remaining or 'NONE'))