python多线程+生产者和消费者模型+queue使用
多线程简介
多线程:在一个进程内部,要同时干很多事情,就需要同时执行多个子任务,我们把进程内的这些子任务叫线程。
线程的内存空间是共享的,每个线程都共享同一个进程的资源
模块:
1、_thread模块 低级模块(在python3里基本已弃用)
2、threading模块 高级模块 对_thread模块进行了封装
threading模块使用
1.使用元组传递 threading.Thread(target=方法名,arg=(参数1,参数2...))
2.用字典传递 threading.Thread(target=方法名,kwargs={“参数名”:参数1,“参数名”:参数2,....})
3.混合使用元组和字典 threading.Thread(target=方法名,args=(参数1,参数2,...),kwargs={“参数名”:参数1,“参数名”:参数2,....})
4.查看线程数:
使用threading.enumerate()函数便可以看到当前线程的数量。
5.查看当前线程的名字:
使用threading.current_thread()可以看到当前线程的信息。
6.join([time]):等待至线程终止。这阻塞调用线程直至线程的join()方法被调用终止、正常退出或者抛出未处理的异常、或者是可选的超时发生。
7.isAlive():返回线程是否活动
8.getName(): 返回线程名
9.setNmae():设置线程名
10.后台线程(守护线程)
后台线程有一个特征:如果所有的前台线程都死亡了,那么后台线程也会自动死亡。
调用Thread对象的daemon属性可将指定线程设置为后台线程。在下面程序可以看到程序里的线程被指定为后台线程,当所有前台程序都死亡了后,后台线程随之死亡。当在整个虚拟机里只剩下后台线程时,程序就没有继续运行的必要了,所以程序也就退出了。
import threading
# 定义后台线程的线程执行体与普通线程没有任何区别
def action(max):
for i in range(max):
print(threading.current_thread().name + " " + str(i))
t = threading.Thread(target=action, args=(100,), name='后台线程')
# 将此线程设置成后台线程
# 也可在创建Thread对象时通过daemon参数将其设为后台线程
t.daemon = True
# 启动后台线程
t.start()
for i in range(10):
print(threading.current_thread().name + " " + str(i))
# -----程序执行到此处,前台线程(主线程)结束------
# 后台线程也应该随之结束
上面程序中的粗体字代码先将t线程设置成后台线程,然后启动该线程。本来该线程应该执行到i等于99时才会结束,但在运行程序时不难发现,该后台线程无法运行到99,因为当主线程也就是程序中唯一的前台线程运行结東后,程序会主动退出,所以后台线程也就被结東了。从上面的程序可以看出,主线程默认是前台线程,t线程默认也是前台线程。但并不是所有的线程默认都是前台线程,有些线程默认就是后台线程一一前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程
可见,创建后台线程有两种方式。
- 主动将线程的 daemon属性设置为True
- 后台线程启动的线程默认是后台线程。
以下看一个简单的多线程程序:
import threading
import time
def coding():
for x in range(3):
print('%s正在写代码' % x)
time.sleep(1)
def drawing():
for x in range(3):
print('%s正在画图' % x)
time.sleep(1)
def single_thread():
coding()
drawing()
def multi_thread():
t1 = threading.Thread(target=coding)
t2 = threading.Thread(target=drawing)
t1.start()
t2.start()
if __name__ == '__main__':
multi_thread()
继承自threading.Thread类:
为了让线程代码更好的封装。可以使用threading模块下的Thread类,继承自这个类,然后实现run方法,线程就会自动运行run方法中的代码。示例代码如下:
import threading
import time
class CodingThread(threading.Thread):
def run(self):
for x in range(3):
print('%s正在写代码' % threading.current_thread())
time.sleep(1)
class DrawingThread(threading.Thread):
def run(self):
for x in range(3):
print('%s正在画图' % threading.current_thread())
time.sleep(1)
def multi_thread():
t1 = CodingThread()
t2 = DrawingThread()
t1.start()
t2.start()
if __name__ == '__main__':
multi_thread()
start()和run()
start()
start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。run()
run()
run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
记住:多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发。start() 和 run()的区别说明
start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
run() : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
Lock版本生产者和消费者模型
生产者和消费者模式是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数据,然后存放到一个中间的变量中。消费者再从这个中间的变量中取出数据进行消费。但是因为要使用中间变量,中间变量经常是一些全局变量,因此需要使用锁来保证数据完整性。以下是使用threading.Lock锁实现的“生产者与消费者模式”的一个例子:
import threading
import random
import time
gMoney = 1000
glo = threading.Lock()
gTotaltime = 10
gTime = 0
class Consumer(threading.Thread):
def run(self):
global gMoney
global gTime
while True:
money = random.randint(100,1000)
glo.acquire()
if gMoney>= money:
gMoney -= money
print("{}消费了{}元,当前剩余{}元".format(threading.current_thread(),money,gMoney))
else:
print("{}准备消费{}元,当前剩余{}元,不足,不能消费".format(threading.current_thread(),money,gMoney))
if gTime >= gTotaltime and money > gMoney:
glo.release()
break
glo.release()
time.sleep(0.7)
class Porducer(threading.Thread):
def run(self):
global gMoney
global gTime
while True:
Money = random.randint(100,700)
glo.acquire()
if gTime == gTotaltime:
glo.release()
break
gMoney += Money
print("{}生产了{}元钱,剩余{}元钱".format(threading.current_thread(),Money,gMoney))
gTime += 1
glo.release()
time.sleep(0.5)
def main():
for x in range(3):
t1 = Porducer(name="生产者")
t1.start()
for i in range(5):
t = Consumer(name="消费者")
t.start()
if __name__ == '__main__':
main()
queue线程安全队列
在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。相关的函数如下:
- 初始化Queue(maxsize):创建一个先进先出的队列。
- qsize():返回队列的大小。
- empty():判断队列是否为空。
- full():判断队列是否满了。
- get():从队列中取最后一个数据。
- put(item,block=Ture,timeout=None):将一个数据放到队列中。如果队列已满,且block参数为Ture(阻塞),当前线程被阻塞,timeout指定阻塞时间,如果将timeout设置为None,则代表一直阻塞,直到有元素被放入队列中:如果队列已空,且block参数设置为False(不阻塞),则直接引发queue.Empty异常。
下面就可以用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):
index = 0
while True:
print(q.get())
time.sleep(0.5)
def main():
q = queue.Queue(4)
t1 = threading.Thread(target=set_value,args=[q])
t2 = threading.Thread(target=get_value,args=[q])
t1.start()
t2.start()
if __name__ == '__main__':
main()