1、其实我本来想把线程同步和前面的线程深入合在一起写,但是想想算了;因为线程同步真的实在是太重要了;所以我们要好好的学习一下线程同步;
无论是什么语言;我们都要知道,锁是线程同步的最重要的只是点;先看python的两种所
1、lock = threading.Lock()
2、lock = threadingRLock()
这两种有什么区别;先看一段代码
import threading
import time
lock1 = threading.RLock()
#lock1 = threading.Lock()
a= int(0)
def funca():
global a
lock1.acquire()
lock1.acquire()
print("the progress two")
for i in range(100000):
a+=1
print ("funca")
lock1.release()
#lock1.release()
def funcb():
global a
lock1.acquire()
for i in range(100009):
a += 1
print("funcb")
lock1.release()
#主线程执行此循环
t1 = threading.Thread(target=funca)
t1.start()
t2 = threading.Thread(target=funcb)
t2.start()
t1.join()
t2.join()
print(a)
这里面我们用了RLock;有以下几个地方需要注意:
1、如果是用的lock的话,那么上述的funca就会出现死锁的问题;为什么呢?因为funca加了一个锁;然后一直不释放;如果代码还有枷锁的地方,就会一直死锁;但是最关键的地方请注意。用了RLock是可以的;它有一个计数器;锁可以多次加;但是必须release配套acquire;因为这里面很关键的是,只有在一个线程函数中可以多次加锁;如果不是一个线程函数,即使使用了RLock,你不释放,也是funcb也是得不到锁的;
2、为什么主线程要有join,如果没有的话;那么会有什么问题呢?那么printa会出现问题;因为即使funca、funcb同步了,但是没有和主线程同步;所以此时有可能t1,t2没有执行完;就打印了a。所以必须加上join以后,再打印就可以保证了就是可以正常打印了;
==================================================================================
其实上面的这个例子,已经很好的对锁进行了解释;关于Lock和RLock的使用,已经很好的进行了梳理;我们下面的话,应该要讲讲condition的使用方法;
我们想想C语言的condition;其实是一样的;我们什么时候会用到condition,也就是条件变量;有一个很经典的使用方式,就是生产者和消费者的关系;因为什么呢?生产者是要生产出东西,我们才可以去消费;那么生产者我们看作是一个线程;消费者是另外一个线程;那么我们使用单纯的锁;是肯定不行的;因为锁你是无法决定先后顺序的;但是使用条件变量是可以的;
同时;还有一个比较经典的使用就是线程池的使用;线程池线程就是一个生产者;需要使用的是使用者;所以这就是使用方式;
下面好好的讲讲条件变量;在C语言中,conditon是原子操作,就是说枷锁和通知是原子操作;
Condition 类提供了如下几个方法:
1、acquire([timeout])/release():调用 Condition 关联的 Lock 的 acquire() 或 release() 方法。
2、wait([timeout]):导致当前线程进入 Condition 的等待池等待通知并释放锁,直到其他线程调用该 Condition 的 notify() 或 3、notify_all() 方法来唤醒该线程。在调用该 wait() 方法时可传入一个 timeout 参数,指定该线程最多等待多少秒。
4、notify():唤醒在该 Condition 等待池中的单个线程并通知它,收到通知的线程将自动调用 acquire() 方法尝试加锁。如果所有线程都在该 Condition 等待池中等待,则会选择唤醒其中一个线程,选择是任意性的。
5、notify_all():唤醒在该 Condition 等待池中等待的所有线程并通知它们。
看下面一个例子:
# coding=utf-8
import threading
import time
con = threading.Condition()
num = 0
# 生产者
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
# 锁定线程
global num
con.acquire()
while True:
print "开始添加!!!"
num += 1
print "火锅里面鱼丸个数:%s" % str(num)
time.sleep(1)
if num >= 5:
print "火锅里面里面鱼丸数量已经到达5个,无法添加了!"
# 唤醒等待的线程
con.notify() # 唤醒小伙伴开吃啦
# 等待通知
con.wait()
# 释放锁
con.release()
# 消费者
class Consumers(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
con.acquire()
global num
while True:
print "开始吃啦!!!"
num -= 1
print "火锅里面剩余鱼丸数量:%s" %str(num)
time.sleep(2)
if num <= 0:
print "锅底没货了,赶紧加鱼丸吧!"
con.notify() # 唤醒其它线程
# 等待通知
con.wait()
con.release()
p = Producer()
c = Consumers()
p.start()
c.start()
运行结果:
运行结果:
开始添加!!!
火锅里面鱼丸个数:1
开始添加!!!
火锅里面鱼丸个数:2
开始添加!!!
火锅里面鱼丸个数:3
开始添加!!!
火锅里面鱼丸个数:4
开始添加!!!
火锅里面鱼丸个数:5
火锅里面里面鱼丸数量已经到达5个,无法添加了!
开始吃啦!!!
火锅里面剩余鱼丸数量:4
开始吃啦!!!
火锅里面剩余鱼丸数量:3
开始吃啦!!!
火锅里面剩余鱼丸数量:2
开始吃啦!!!
火锅里面剩余鱼丸数量:1
开始吃啦!!!
火锅里面剩余鱼丸数量:0
锅底没货了,赶紧加鱼丸吧!
开始添加!!!
火锅里面鱼丸个数:1
开始添加!!!
火锅里面鱼丸个数:2
开始添加!!!
火锅里面鱼丸个数:3
开始添加!!!
火锅里面鱼丸个数:4
开始添加!!!
火锅里面鱼丸个数:5
分析一下:
这里面首先要搞清楚两个问题;一个是条件变量是如何使用的,什么地方是原子操作,什么地方不是;和C一样,conditon是需要配合锁一起使用的;在python中,不需要我们自己弄锁,而是condition自己带有,所以,我们用conditon.acquire和conditon,release即可;
然后还有一个比较关键的问题;conditon是一个原子操作;他的操作分成以下几步:1、开锁;2、等待信号通知;3、加锁;这一连串的操作;说白了就是一个原子操作;可以看成是一个操作;
那么好了,conditon为什么要和while一起使用,呼之欲出;下面重点讲讲为什么conditon要和while一起使用;这里我放一段当时C语言时候的一个总结,主要是不想重复去说了;
image.png
先简单的解释一个这个例子
首先全局的锁,还有全局的条件变量是必不可少的;
然后main函数里面初始化,也是必不可少的;这些我们不是很在意;
之后就有意思了;
因为有一个全局变量i,然后i就说明,是func1,还有func2共同要访问的资源,所以我们需要加锁去控制线程间同步;
但是出现了一个问题,我现在有一个需求就是,我需要线程1首先创造i,i必须大于0,func2才可以使用i这个值;那么这个时候我就需要条件变量了
看代码:
这里,我需要func1首先对i加锁,然后func2页对i加锁,这样func1和func2要去抢锁吧;谁抢到就是谁的;很不幸,2抢到了;然后2抢到了以后,对i加锁;这是发现,i=0,所以进入循环,pthread_cond_wait会执行以下三步,解锁,等待有没有条件变量,然后再加锁
这时,肯定是解锁了,然后就一直等待信号量,然后我觉得可以做一个实验,如下:
image.png
我把函数1注释掉了,不让他发信号了,这个时候,看到了嘛,pthread_cond_wait会阻塞,然后一直阻塞下去;说明什么呢?说明pthread_cond_wait会阻塞在等待信号的那个阶段;
然后当收到了信号,才往下执行,这时,加入我没有吧函数1注释掉,那么i就是1了,所以,就跳出了循环,打印了i的值;
经过上面的分析,我要明白一点,为什么不能用if,而必须用while?
你从上面这个例子,是看不出的,因为,我们就一个函数,没有其他的和他竞争,所以if和while通过上面的分析,其实没啥区别;但是,如果你想,如果还有一个函数func3,他和2一样,都是要等待1的信号,会出现什么情况呢?那就会有一种可能:
按照下面一步步的去想:,假设函数2和3是一样的,然后都是if
1、 函数2抢到了锁,然后发现i是0,阻塞等待条件变量;
2、 然后2释放了锁,3抢到了锁,发现i是0 ,阻塞等待条件变量;
3、 然后1抢到了,i+1,发出了信号,这里我们是广播哈,大家都可以收到;
4、 这个时候,2,3,都收到了信号,唤醒了条件变量,然后2,3又开始抢锁
5、 假设2抢到了,那么3就一直阻塞住在等锁的过程中;
6、 然后呢,2跑完了,释放了锁,3抢到了很开心,她可以去用了,这个时候问题就出来了
如果是if,那么假设2把i又变成了0,那3抢到了锁,也不应该往下执行,因为我们需要它不是0;但是while就不一样了,while就是说,让3抢到了锁,但是重亲判断i还是不是0,这就是说明if只判断一次,while让它重新判断的道理;
所以重新判断,i是0,说明2用了i,把它变成了0,3要想用i,就得等着函数1再把i变成1才行;
所以大家都习惯吧条件变量用生产者和消费者来描述,对于一个消费者,while和if都可以,但是如果对于多个消费者,那么存在消费者消费i的问题(假设就是i--,生产者是i++),所以就出现了描述的问题,if只判断了一次,而while判断了多次;那么其他的消费者必须等着生产者又生产出了i,才可以消费他
其实这么看,上面的python例子并不是一个比较典型的例子;下面我自己写一个,实现的是比如我有一个生产者,然后有三个人在吃的例子;看看到底后面会出现什么情况;
import threading
con = threading.Condition()
num = 0
def producter():
global num
while True:
con.acquire()
print("producter has the lock!")
if num<=30:
num+=1
con.notify_all()
print("the num has %d"%num)
con.release()
print("producter give the lock!")
def xiaofei(number):
global num
while True:
con.acquire()
print("xiaofei%d has the lock!"%number)
while(num<=0):
con.wait()
num-=1
print("I am the number %d"%number)
con.release()
print("xiaofei%d give the lock!" % number)
thread_pro = threading.Thread(target=producter)
thread_1 = threading.Thread(target=xiaofei,args = (1,))
thread_2 = threading.Thread(target=xiaofei,args = (2,))
thread_3 = threading.Thread(target=xiaofei,args = (3,))
thread_pro.start()
thread_1.start()
thread_2.start()
thread_3.start()
···