上一篇文章 threading模块简介传送门:
https://blog.csdn.net/youngwyj/article/details/124720041
线程同步是多线程中很重要的概念,当多个线程需要共享数据时,如果不使用线程同步,就会存在数据不同步的情况。
要做到线程同步有两种方法,线程锁和条件变量Condition。
threading模块中Lock锁和_thread模块中的锁是一样的。
示例:
import threading
import time
num = 0
lock = threading.Lock() #申请线程锁
class threadTest(threading.Thread): #类必须继承threading.Thread
def __init__(self) -> None: #args为传入线程的参数,可根据自己的需求进行定义
super(threadTest,self).__init__() #初始化super()内的必须与类名一样
def run(self) -> None: #定义run()方法,主要写线程的执行内容
global num #声明全局变量num
# lock.acquire() #申请线程锁
print('子线程' + self.getName() + '开始:' + str(time.time()))
while num < 5:
time.sleep(2)
print(self.getName(),'num:',num)
num += 1
#休眠2s
print('子线程' + self.getName() + '结束:' + str(time.time()))
# lock.release() #释放线程锁
return super().run()
if __name__ == '__main__':
print('主线程开始:%s'%(str(time.time())))
thread1 = threadTest()
thread1.setName('Thread-1') #设置线程名称
thread2 = threadTest()
thread2.setName('Thread-2') #设置线程名称
thread1.start() #启动线程
thread2.start() #启动线程
time.sleep(1)
thread1.join()
thread2.join()
print('主线程已结束:%s'%(str(time.time())))
运行结果:
主线程开始:1652803641.5234373
子线程Thread-1开始:1652803641.5236645
子线程Thread-2开始:1652803641.5238614
Thread-2 num: 0
Thread-1 num: 0
Thread-2 num: 2
Thread-1 num: 3
Thread-2 num: 4
子线程Thread-2结束:1652803647.5305896
Thread-1 num: 5
子线程Thread-1结束:1652803647.5308123
主线程已结束:1652803647.531019
可以看到在未使用锁线程时,线程1和线程2对num操作出现了混乱。
将上面代码 lock.acquire() lock.release()这两行代码的注释去掉,将线程锁添加。
运行结果如下:可以看到不会像上面出现混乱的情况。
主线程开始:1652804037.7551372
子线程Thread-1开始:1652804037.7553797
Thread-1 num: 0
Thread-1 num: 1
Thread-1 num: 2
Thread-1 num: 3
Thread-1 num: 4
子线程Thread-1结束:1652804047.7664511
子线程Thread-2开始:1652804047.766612
子线程Thread-2结束:1652804047.7667737
主线程已结束:1652804047.7669005
RLock锁又称递归锁,其与Lock锁的差别在于,Lock锁只允许在同一线程中申请一次,否则线程会进入死锁,但是RLock允许在同一线程多次调用。
使用Lock锁产生死锁示例代码:
import threading
import time
print('主线程开始:%s'%(str(time.time())))
lock = threading.Lock()
lock.acquire() #申请线程锁
print(threading.enumerate())
lock.acquire() #再次申请线程锁,产生了死锁
print(threading.enumerate())
lock.release()
lock.release()
print('主线程结束:%s'%(str(time.time())))
运行结果:
主线程开始:1652804393.5089679
[<_MainThread(MainThread, started 140384603543360)>]
^Z #此处为我在终端中主动杀死了线程,并非程序自己结束
[1]+ 已停止
使用RLock锁不会产生死锁示例代码:
import threading
import time
print('主线程开始:%s'%(str(time.time())))
lock = threading.RLock()
lock.acquire() #申请线程锁
print(threading.enumerate())
lock.acquire() #再次申请线程锁,不会产生死锁
print(threading.enumerate())
lock.release()
lock.release()
print('主线程结束:%s'%(str(time.time())))
运行结果:
主线程开始:1652804609.3881009
[<_MainThread(MainThread, started 140639696934720)>]
[<_MainThread(MainThread, started 140639696934720)>]
主线程结束:1652804609.3881621
从上面可以看到Lock与RLock的区别
注意线程锁需要成对出现
Condition是python3中一种更高级的锁,除和线程锁类似的 acquire() 和 release() 函数外,还提供以下函数。
函数 | 说明 |
---|---|
wait() | 使线程挂起 |
notify() | 唤醒挂起的线程使其运行 |
notifyAll() | 唤醒所有线程使其运行 |
注意:线程使用前需要获得锁,否则会抛出RuntimeError异常
可以理解为,Condition提供了一种多线程通信机制,若线程1需要数据,线程1就会阻塞等待,线程2制造出数据,等待线程2制造好数据并通知线程1后,线程1就可以去获取数据了
下面是一个使用条件变量 Condition 模拟成语接龙示例代码:
import threading
import time
class Man1(threading.Thread): #类必须继承threading.Thread
def __init__(self,lock) -> None: #args为传入线程的参数,可根据自己的需求进行定义
super(Man1,self).__init__() #初始化super()内的必须与类名一样
self.lock = lock
def run(self):
self.lock.acquire() #申请锁
print('子线程' + self.getName() + '为所欲为')
self.lock.wait() #挂起线程,等待回答
print('子线程' + self.getName() + '逼上梁山')
self.lock.notify() #运行挂起的线程
self.lock.wait()
print('子线程' + self.getName() + '尽力而为')
self.lock.notify()
self.lock.release()
class Man2(threading.Thread): #类必须继承threading.Thread
def __init__(self,lock) -> None: #args为传入线程的参数,可根据自己的需求进行定义
super(Man2,self).__init__() #初始化super()内的必须与类名一样
self.lock = lock
def run(self):
self.lock.acquire()
self.lock.notify() #唤醒对方线程
print('子线程' + self.getName() + '为法自弊')
self.lock.wait() #挂起线程,等待回答
self.lock.notify()
print('子线程' + self.getName() + '山穷水尽')
self.lock.wait()
self.lock.notify()
print('子线程' + self.getName() + '为所欲为')
self.lock.release()
if __name__ == '__main__':
lock = threading.Condition()
man1 = Man1(lock)
man2 = Man2(lock)
man1.setName('Thread-1') #设置线程名称
man2.setName('Thread-2') #设置线程名称
print('成语接龙开始:')
man1.start()
man2.start()
运行结果:
成语接龙开始:
子线程Thread-1为所欲为
子线程Thread-2为法自弊
子线程Thread-1逼上梁山
子线程Thread-2山穷水尽
子线程Thread-1尽力而为
子线程Thread-2为所欲为
可以看到在条件变量的控制下,两个线程按照顺序执行,直到结束