9、第七 - 网络编程基础 -线程锁(互拆锁Mutex)--threading.Lock()
互斥锁的概念理解:Python编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在Python中我们使用threading模块提供的Lock类。锁的意义,就是只允许一个线程对数据进行更改。
Python threading模块有两类锁:互斥锁(threading.Lock )和递归锁(threading.RLock)。两者的用法基本相同,具体如下:
lock = threading.Lock() lock.acquire() dosomething…… lock.release()
举例如下:
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据。就会出现数据不一致的情况。
A、加等待时间time.sleep
import threading #导入threading模块 import time #导入time模块 class MyThread(threading.Thread): # 通过继承创建类 def __init__(self,threadname): # 初始化方法 super(MyThread,self).__init__() # 调用父类的初始化方法 def run(self): # 定义run方法 global x # 使用global表明x为全局变量 for i in range(3): x += 1 time.sleep(2) # 调用sleep函数,让线程休眠3秒 print(x) t1 = [] #定义空列表 x = 0 #将x赋值为0 for i in range(5): t = MyThread("i") # 类实例化 t1.append(t) # 将类对象添加到列表中 for i in t1: i.start() 输出: /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 /Users/mac/PycharmProjects/untitled2/51CTO/6day/线程锁.py 15 15 15 15 15
注:为什么结果都是一样呢?关键在于global 行和 time.sleep行:
- 由于x是一个全局变量,所以每次循环后 x 的值都是执行后的结果值;
- 由于该代码是多线程的操作,所以在sleep 等待的时候,之前已经执行完成的线程会在这等待,而后续的进程在等待2s这段时间也执行完成,等待print。同样由于global 的原理,x被重新斌值。所以打印出的结果全是15 ;
- 便于理解,可以尝试将sleep等注释,你再看下结果,就会发现有不同。
B、注释time.sleep
import threading #导入threading模块 import time #导入time模块 class MyThread(threading.Thread): # 通过继承创建类 def __init__(self,threadname): # 初始化方法 super(MyThread,self).__init__() # 调用父类的初始化方法 def run(self): # 定义run方法 global x # 使用global表明x为全局变量 for i in range(3): x += 1 #time.sleep(2) # 调用sleep函数,让线程休眠3秒 print(x) t1 = [] #定义空列表 x = 0 #将x赋值为0 for i in range(5): t = MyThread("i") # 类实例化 t1.append(t) # 将类对象添加到列表中 for i in t1: i.start() 输出: /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 /Users/mac/PycharmProjects/untitled2/51CTO/6day/线程锁.py 3 6 9 12 15
注:前后调用,按顺序打印。
C、所以在实际应用中,也会出现类似于sleep等待的情况。在前后调用有顺序或打印有输出的时候,就会现并发竞争,造成结果或输出紊乱。这里就引入了锁的概念,上面的代码修改下:
import threading #导入threading模块 import time #导入time模块 class MyThread(threading.Thread): # 通过继承创建类 def __init__(self,threadname): # 初始化方法 super(MyThread,self).__init__() # 调用父类的初始化方法 def run(self): # 定义run方法 global x # 使用global表明x为全局变量 lock.acquire() for i in range(3): x += 1 time.sleep(2) # 调用sleep函数,让线程休眠3秒 print(x) lock.release() lock = threading.Lock() # 调用lock的release方法 ---改为 threading.RLock()效果一样 t1 = [] #定义空列表 x = 0 #将x赋值为0 for i in range(5): t = MyThread("i") # 类实例化 t1.append(t) # 将类对象添加到列表中 for i in t1: i.start() 输出: 3 6 9 12 15
附加:另外使用join 貌似也可以实现上述所说的加锁的情况如下:
#注释t.join import time,threading def addNum(): global num print("get num:",num) time.sleep(2) num -= 1 num = 5 thread_list=[] for i in range(5): t = threading.Thread(target=addNum) t.start() #t.join() #使进程等待,处理完成一个到一个 thread_list.append(i) print('final num',num) 输出: get num: 5 get num: 5 get num: 5 get num: 5 get num: 5 final num 5 ======================================= #去掉注释 import time,threading def addNum(): global num print("get num:",num) time.sleep(2) num -= 1 num = 5 thread_list=[] for i in range(5): t = threading.Thread(target=addNum) t.start() t.join() thread_list.append(i) print('final num',num) 输出: get num: 5 get num: 4 get num: 3 get num: 2 get num: 1 final num 0 for循环5次,根据num为 5依次递减
备注:加锁的结果会造成阻塞,而且会造成机器资源开销大。会根据顺序由并发的多线程按顺序输出,如果后面的线程执行过快,需要等待前面的进程结束后其才能结束。类似这样的场景,可以使用队列工具去解决。
人有傲骨终不贱,脚踏实地见真章;
超出预期为工作,价值体现显能力。