互斥锁的概念理解: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依次递减
备注:加锁的结果会造成阻塞,而且会造成机器资源开销大。会根据顺序由并发的多线程按顺序输出,如果后面的线程执行过快,需要等待前面的进程结束后其才能结束。类似这样的场景,可以使用队列工具去解决。