threading 库的使用
函数的线程:
import time
import threading
def coding():
for x in range(3):
print("正在写代码",x)
time.sleep(1)
def drawing():
for x in range(3):
print("正在画画",x)
def main():
设置两个线程,target="这里填函数"
t1=threading.Thread(target=coding)
t2=threading.Thread(target=drawing)
让线程开始
t1.start()
t2.start()
if __name__ == '__main__':
main()
运行结果:
正在写代码 0
正在画画 0
正在画画 1
正在画画 2
正在写代码 1
正在写代码 2
2.查看有多少个线程:
print(threading.enumerate())
3.查看现在的线程:
threading.current_thread()
类的线程
1.首先这个类需要继承threading.Thread
2.必须在类的run 方法中实现
3.建立一个对象就可以使用线程了
import time
import threading
class codingthread(threading.Thread): #继承threading.Thread
def run(self): # 使用run 方法
for x in range(3):
print("在写代码",threading.current_thread())
time.sleep(1)
class Drawingthreading(threading.Thread):
def run(self):
for x in range(3):
print("正在画画",threading.current_thread())
time.sleep(1)
def main():
# 建立对象就可以让线程开始
t1=codingthread()
t2=Drawingthreading()
t1.start()
t2.start()
if __name__ == '__main__':
main()
运行结果:
在写代码 <codingthread(Thread-1, started 2532)>
正在画画 <Drawingthreading(Thread-2, started 15384)>
正在画画在写代码 <codingthread(Thread-1, started 2532)>
<Drawingthreading(Thread-2, started 15384)>
在写代码正在画画 <Drawingthreading(Thread-2, started 15384)>
<codingthread(Thread-1, started 2532)>
多线程共享全局变量的问题:
多线程都是在一个进程中运行的,因此在线程中的全局变量所有的线程都是可以共享的,这就造成了一个问题,因为线程的执行的顺序是无顺序的,可能会造成数据错误!
例子:
import threading
value=0
def add():
global value
for x in range(1000):
value+=1
print(value)
def main():
for x in range(2):
t=threading.Thread(target=add)
t.start()
if __name__ == '__main__':
main()
结果:
1000
2000
这个线程没有出错,是因为数字太小了很快就只执行完成了,
再试试下面一个:
import threading
value=0
def add():
global value
for x in range(1000000):
value+=1
print(value)
def main():
for x in range(2):
t=threading.Thread(target=add)
t.start()
if __name__ == '__main__':
main()
运行结果:
1518705
1705246
为什么会出错呢? 就是因为多线程的无序问题导致出错了.
又该如何解决呢?
使用锁
使用锁
创建一个锁名为:gLock=threading.Lock()
将需要锁住的地方锁上:
gLock.acquire()
锁完之后需要解锁
gLock.release()
import threading
value=0
# 创建锁
gLock=threading.Lock()
def add():
global value
# 将这个地方锁住
gLock.acquire()
for x in range(1000000):
value+=1
# 将锁解开
gLock.release()
print(value)
def main():
for x in range(2):
t=threading.Thread(target=add)
t.start()
if __name__ == '__main__':
main()
运行结果
1000000
2000000
lock 版生产者和消费者模式(经典例子)
接下来的例子是使用了一个线程生产,一个线程消费,利用锁的机制改全局变量
import time
import threading
import random
gMoney=1000 # 一开始的钱
gtimes=10 # 需要赚钱的次数
try_times=0 # 已经赚钱的次数
# 建立一个锁
gLock=threading.Lock()
# 继承了 threading.Thread
class Product(threading.Thread):
def run(self):
# 定义全局变量
global gMoney
global gtimes
global try_times
while True:
# 要改变全局变量时进行加锁
gLock.acquire()
money=random.randint(100,1000)
# 没有超过gtimes的次数继续赚钱
if(gtimes>=try_times):
gMoney+=money
print("{}生产了{}元钱,还剩{}钱".format(threading.current_thread(),money,gMoney))
try_times+=1
else:
# 超过了gtimes 的次数不继续赚钱
# 记得要先解锁 ,在break
gLock.release()
break
gLock.release()
time.sleep(1)
# 继承 threading.Thread
class Consumer(threading.Thread):
def run(self):
# 声明全局变量
global gMoney
while True:
gLock.acquire()
money=random.randint(100,1000)
# 判断钱还够不够用?
if(money<=gMoney):
gMoney-=money
print("{}消费了{}元钱,还剩{}钱".format(threading.current_thread(),money,gMoney))
else:
print("{}想消费{}元钱,但是钱不够用了".format(threading.current_thread(),money))
# 退出时候要先解锁
gLock.release()
break
gLock.release()
time.sleep(1)
def main():
# 使用三个产生者 三个线程来赚钱
for i in range(3):
t2=Product(name="第{}个生产者".format(i+1))
t2.start()
# 使用两个消费者,两个线程来消费钱财
for i in range(2):
t1=Consumer(name="第{}个消费者".format(i+1))
t1.start()
if __name__ == '__main__':
main()
运行结果:
<Product(第1个生产者, started 9268)>生产了775元钱,还剩1775钱
<Product(第2个生产者, started 8032)>生产了976元钱,还剩2751钱
<Product(第3个生产者, started 15100)>生产了149元钱,还剩2900钱
<Consumer(第1个消费者, started 16948)>消费了847元钱,还剩2053钱
<Consumer(第2个消费者, started 16004)>消费了576元钱,还剩1477钱
<Product(第1个生产者, started 9268)>生产了683元钱,还剩2160钱
<Product(第3个生产者, started 15100)>生产了315元钱,还剩2475钱
<Consumer(第2个消费者, started 16004)>消费了196元钱,还剩2279钱
<Consumer(第1个消费者, started 16948)>消费了400元钱,还剩1879钱
<Product(第2个生产者, started 8032)>生产了612元钱,还剩2491钱
<Product(第2个生产者, started 8032)>生产了146元钱,还剩2637钱
<Consumer(第2个消费者, started 16004)>消费了813元钱,还剩1824钱
<Consumer(第1个消费者, started 16948)>消费了170元钱,还剩1654钱
<Product(第3个生产者, started 15100)>生产了584元钱,还剩2238钱
<Product(第1个生产者, started 9268)>生产了373元钱,还剩2611钱
<Product(第3个生产者, started 15100)>生产了992元钱,还剩3603钱
<Product(第1个生产者, started 9268)>生产了783元钱,还剩4386钱
<Consumer(第1个消费者, started 16948)>消费了658元钱,还剩3728钱
<Consumer(第2个消费者, started 16004)>消费了504元钱,还剩3224钱
<Consumer(第2个消费者, started 16004)>消费了605元钱,还剩2619钱
<Consumer(第1个消费者, started 16948)>消费了671元钱,还剩1948钱
<Consumer(第2个消费者, started 16004)>消费了343元钱,还剩1605钱
<Consumer(第1个消费者, started 16948)>消费了679元钱,还剩926钱
<Consumer(第1个消费者, started 16948)>消费了903元钱,还剩23钱
<Consumer(第2个消费者, started 16004)>想消费311元钱,但是钱不够用了
<Consumer(第1个消费者, started 16948)>想消费761元钱,但是钱不够用了
Process finished with exit code 0
进行结果分析: 利用锁的机制将全局变量锁定起来,当自己在执行的时候不收其他线程的影响,从而实现线程的不错乱.
Condition版本的生产者与消费者
lock 版本的生产者与消费者模式可以正常运行,但是存在一个不足,在消费中,总是通过 while True 死循环并且上锁的方式去判断钱够不够,上锁是一个很消费cpu 的行为, 因此这种方式不是最好的,还有一种更好的方式是使用threading.Condition 来实现,threading.Condition 可以在没有数据的时候处于堵塞等待状态,一旦有合适的数据了,还可以使用notify 相关的函数来通知其他处于等待状态的线程.这样就可以不用做一些无用的上锁和解锁的操作,可以提高程序的性能,收先对threading.Condition 相关的函数做个介绍,threading.Condition 类似threading.Lock 可以在修改全局数据的时候进行上锁,上锁后继续执行下面的代码.
1.acquire :上锁
2.release :解锁
3. wait :将当前线程处于等待状态,并且会释放锁,可以被其他线程使用notify 和notify_all 函数唤醒. 被唤醒后会继续等待上锁,上锁后继续执行下面的代码.
4. notify :通知某个正在等待的线程,默认是第一个等待的线程
5. notify_all :通知所有正在等待的线程. notif和notify_all 不会释放锁 .并且需要在release 之前调用.
Condition 版的生产者与消费者模式代码如下:
import time
import threading
import random
gMoney=1000 # 一开始的钱
gtimes=10 # 需要赚钱的次数
try_times=0 # 已经赚钱的次数
# 建立一个锁
gCondition=threading.Condition()
# 继承了 threading.Thread
class Product(threading.Thread):
def run(self):
# 定义全局变量
global gMoney
global gtimes
global try_times
while True:
# 要改变全局变量时进行加锁
gCondition.acquire()
money=random.randint(100,1000)
# 没有超过gtimes的次数继续赚钱
if(gtimes>try_times):
gMoney+=money
print("{}生产了{}元钱,还剩{}钱".format(threading.current_thread(),money,gMoney))
try_times+=1
# 这里提醒等待(gCondition.wait)的开始排队执行
gCondition.notify_all()
else:
# 超过了gtimes 的次数不继续赚钱
# 记得要先解锁 ,在break
gCondition.release()
break
gCondition.release()
time.sleep(1)
# 继承 threading.Thread
class Consumer(threading.Thread):
def run(self):
# 声明全局变量
global gMoney
while True:
gCondition.acquire()
money=random.randint(100,1000)
# 直到剩余的钱比消费的钱多的时候在出来
while gMoney<money:
# 进行一次判断如果是超过赚钱次数了,且钱不够直接退出程序
if gtimes>=try_times:
gCondition.release()
return
# 钱不够进行等待,让gCondition 进行提醒,直到提醒到钱够时在退出
gCondition.wait()
print("{}准备消费{}元钱,剩余{}元钱,不足!".format(threading.current_thread(),money,gMoney))
gMoney-=money
print("{}消费了{}元钱,还剩{}元钱".format(threading.current_thread(),money,gMoney))
gCondition.release()
time.sleep(1)
def main():
# 使用三个产生者 三个线程来赚钱
for i in range(3):
t2=Product(name="第{}个生产者".format(i+1))
t2.start()
# 使用两个消费者,两个线程来消费钱财
for i in range(2):
t1=Consumer(name="第{}个消费者".format(i+1))
t1.start()
if __name__ == '__main__':
main()
运行结果:
<Product(第1个生产者, started 2768)>生产了304元钱,还剩1304钱
<Product(第2个生产者, started 15412)>生产了158元钱,还剩1462钱
<Product(第3个生产者, started 16072)>生产了952元钱,还剩2414钱
<Consumer(第1个消费者, started 10584)>消费了466元钱,还剩1948元钱
<Consumer(第2个消费者, started 9420)>消费了844元钱,还剩1104元钱
<Product(第1个生产者, started 2768)>生产了596元钱,还剩1700钱
<Product(第2个生产者, started 15412)>生产了533元钱,还剩2233钱
<Consumer(第2个消费者, started 9420)>消费了892元钱,还剩1341元钱
<Consumer(第1个消费者, started 10584)>消费了210元钱,还剩1131元钱
<Product(第3个生产者, started 16072)>生产了296元钱,还剩1427钱
<Consumer(第2个消费者, started 9420)>消费了753元钱,还剩674元钱
<Product(第1个生产者, started 2768)>生产了161元钱,还剩835钱
<Product(第3个生产者, started 16072)>生产了397元钱,还剩1232钱
<Consumer(第1个消费者, started 10584)>消费了723元钱,还剩509元钱
<Product(第2个生产者, started 15412)>生产了263元钱,还剩772钱
<Product(第2个生产者, started 15412)>生产了692元钱,还剩1464钱
<Consumer(第1个消费者, started 10584)>消费了619元钱,还剩845元钱
<Consumer(第2个消费者, started 9420)>消费了234元钱,还剩611元钱
<Consumer(第2个消费者, started 9420)>消费了189元钱,还剩422元钱
<Consumer(第1个消费者, started 10584)>消费了253元钱,还剩169元钱
Process finished with exit code 0
运行结果分析: 利用锁的原理,和等待的和提醒的原理进行对全局变量的灵活应用,减少了锁的使用,提高了代码的效率
Queue线程安全队列:
在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做、queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。这些队列都实现了锁原语〈可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。相关的函数如下:
1.初始化Queue(maxsize):创建一个先进先出的队列。
2.qsize):返回队列的大小。
3.empty(:判断队列是否为空。
4. full():判断队列是否满了。
5.get():从队列中取最后一个数据。
5. put(:将一个数据放到队列中。
基本例子:
from queue import Queue
# 定义4个大小的队列
q=Queue(4)
for i in range(4):
# 放四个值放在队列中
q.put(i)
# 查看队列的里面有多少个值
print(q.qsize())
# 判断是否为满 ,如果为满则返回True,不为满返回False
print(q.full())
# 判断是否为空队列,如果为空返回True,不为空返回False
print(q.empty())
for i in range(4):
# 取队列里的值
q.get()
注意 put 和get 里面有一个参数 block 默认是True 意思 就是会等待值,说的通俗一点 你用get的时候,你需要值得时候没有的时候,他会等待(程序暂停)你给他值,当put没有值传入的时候也会等待(程序暂停)值得传入.
from queue import Queue
import time
import threading
def set_value(q):
# 定义一个入队函数
index=0
while True:
q.put(index)
index+=1
#入队一个睡三秒
time.sleep(3)
def get_value(q):
# 定义一个出队函数
while True:
print(q.get())
def main():
# 定义4个大小的队列
q = Queue(4)
# 这里函数的对面函数名用target ,函数参数用列表传入到 args 里面
t1=threading.Thread(target=set_value,args=[q])
t2=threading.Thread(target=get_value,args=[q])
t1.start()
t2.start()
if __name__ == '__main__':
main()
0
1
2
3
4
5
6
7
8
每大约三秒就会出现一个
结果的分析:
因为 put 和 get 函数里面的参数 block 默认为True,所以它会等待值得来到,所以不会出错.