Python多线程
—恢复内容开始—
一:Python threading 模块
线程的创建和调用方式:
#第一种 import threading,time def thrad_test(n): print('我是线程%s'%n) time.sleep(1) # 格式: threading.Thread(target=函数的名字, args=(函数参数1,函数参数2)) t1 = threading.Thread(target=thrad_test, args=(1,)) # 创建线程实例1 t2 = threading.Thread(target=thrad_test, args=(2,)) # 创建线程实例2 t3 = threading.Thread(target=thrad_test, args=(3,)) # 创建线程实例3 t1.start() #启动线程 t2.start() #启动线程 t3.start() #启动线程
#第二种,继承式 import threading,time class ThreadCust(threading.Thread): def __init__(self,n): threading.Thread.__init__(self) self.n = n def run(self): print('我是线程%s'%self.n) t1 = ThreadCust(1) t2 = ThreadCust(2) t1.start() t2.start()
第二:join()
import threading,time class ThreadCust(threading.Thread): def __init__(self,n): threading.Thread.__init__(self) self.n = n def run(self): print('我是编号[-%s-]线程'%self.n) time.sleep(self.n) print('我是编号[-%s-]线程, 我运行结束了'%self.n) print('我是主线程,我开始创建子线程和运行子线程了') t1 = ThreadCust(2) t2 = ThreadCust(5) t1.start() #start的作用是 主线程 启动子线程后,就扔给CPU了,不管了 t2.start() t2.join()
# t2线程join(),那么则表示:必须得等待t2线程执行完成,主线程才能继续往下执行,作用是用来阻塞主线程的,要join()的子线程命令主线程等待一下 print('我是主线程,但我得等待编号[-5-]的线程工作完成了,才退出')
# 加join()结果:
我是主线程,我开始创建子线程和运行子线程了
我是编号[-2-]线程
我是编号[-5-]线程
我是编号[-2-]线程, 我运行结束了
我是编号[-5-]线程, 我运行结束了
我是主线程,但是我的得等待编号[-5-]的线程工作完成了,才退出
——————————————–分割线——————————————-
# 不加join()的结果:
print('我是主线程,我开始创建子线程和运行子线程了') t1 = ThreadCust(2) t2 = ThreadCust(5) t1.start() #start的作用是 主线程 启动子线程后,就扔给CPU了,不管了 t2.start() print('我是主线程,我的工作结束了。子线程儿子们,爸爸先走了')
我是主线程,我开始创建子线程和运行子线程了
我是编号[-2-]线程
我是编号[-5-]线程
我是主线程,我的工作结束了。子线程儿子们,爸爸先走了
我是编号[-2-]线程, 我运行结束了
我是编号[-5-]线程, 我运行结束了
####由此说明:一个进程内总共有线程数量: (一个主线程 + 创建子线程的数量)
##小结:
一个进程内必有一个主线程,主线程的作用是执行代码(线程也是代码),创建的子线程由主线程运行后,子线程会自动的去执行它所需要执行的代码。另外,主线程从上到下的执行代码,它不管子线程运行的如何,子线程任务有没有运行完成,主线程都不管,但是如果某个子线程加了join()方法的话,那么就等于告诉主线程:主线程爸爸,你得等我执行结束了,你才能执行结束 – -!!!
第三: setDaemon 守护线程
# setDaemon 守护线程,就是守护主线程,主线程结束,子线程也跟着结束。哪个线程设置了守护,那么哪个线程就跟主线程一起运行完毕(运行完毕,不是运行终止)
# 设置了setDamon(True)的子线程, 等于告诉主线程:主线程爸爸,你跑(运行完毕)的时候带带我 - -!!!
import threading,time
class ThreadCust(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n = n
def run(self):
print('我是编号[-%s-]线程'%self.n)
time.sleep(self.n)
print('我是编号[-%s-]线程, 我运行结束了'%self.n)
print('我是主线程,我开始创建子线程儿子和运行子线程儿子了')
thre_list = []
t1 = ThreadCust(2)
t2 = ThreadCust(5)
thre_list.append(t1)
thre_list.append(t2)
t2.setDaemon(True) # 设置守护线程时,必须要在start()的上面设置 seDamon(True),否则有报异常
for t in thre_list:
t.start()
print('我是主线程,我的工作结束了')
#结果:
我是主线程,我开始创建子线程儿子和运行子线程儿子了
我是编号[-2-]线程
我是编号[-5-]线程
我是主线程,我的工作结束了
我是编号[-2-]线程, 我运行结束了
由结果得知:t2线程也就是编号为[-5-],任务没有完成就跟着主线程执行完毕
第三:线程锁
#也叫互斥锁
#问题的场景:一个进程可以拥有多个线程,除主线程外,多个线程共享着进程的内存空间,
这就相当于每个线程可以访问共同的一份数据。当一个以上的线程修改同一份数据的时候,
就会产生严重的问题
#下面的例子是多个线程修改一个全局变量
import threading,time
class ThreadCust(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n = n
def run(self):
global var
print ('var:',var)
time.sleep(1)
var -= 1
var = 100
thre_list = []
for i in range(3): # 创建3个线程
t = ThreadCust(i)
t.start()
t.join()
print(var)
'''
# 它的正常结果应该是97,因为每个线程都执行 var - 1 操作,最后的结果可能是99、98、97等等,结果不确定性。
这是因为多个线程创建并运行的时候,
最先创建并运行的线程拿到的值可能是100,慢一点的线程拿到的值可能是99、98等,有非常大的不确定性。比如:
线程1、线程2、线程3由于创建的速度和运行的速度够快,几乎同时拿到 var = 100, 这样:
线程1的 var -= 1 那么就是 100-1=99,
线程2的 var -= 1 那么就是 100-1=99,
线程3的 var -= 1 那么就是 100-1=99
最终修改可能结果:99 ,正常结果是97
或者这样:
可能线程1创建最快,运行最快,那么可能最先成功修改了var的值,var -= 1 此时var等于99
线程2 和 线程3 几乎同时快,同时拿到了线程1修改后的值(99),此时:
线程2 var -= 1 那么就是 99 - 1
线程3 var -= 1 那么就是 99 - 1
最终修改结果可能是98, 正常结果是97
或者再这样:
线程1的速度 > 线程2的速度 > 线程3的速度
var-1=99 var-1=98 var-1=97 最终结果:97 (正确)
#注意:以上情况是属于比较理想的情况,依此类推,不理想的情况下,可能 线程1 比 线程3 的速度慢,线程2 比 线程1 的速度快
等都有这种可能,
并不是谁最先创建谁就有执行优先权的
'''
----------------------------------------分割线-------------------------------------------------
解决以上的资源争夺问题:RLock,也叫递归锁
import threading,time
class ThreadCust(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n = n
def run(self):
global var
lock.acquire() #2. 线程开始锁住,至此,其他线程不得操作变量 var,
必须得等到 得到此锁的线程修改完毕后 其他得到锁的线程才能修改
print ('var:',var)
time.sleep(1)
var -= 1
lock.release() #3. 锁释放锁
var = 100
thre_list = []
lock = threading.RLock() #1. 生成一个锁
for i in range(3):
t = ThreadCust(i)
t.start()
t.join()
print(var) 结果:97(正确)
# 假设线程1得到锁,那么线程2、线程3则不能动,谁得到锁谁就有运行到底的优先权,所以线程2和3只能静静的看着线程1在装B - -!!!
得到锁的情况,类似如下图:~~~~~~~~~~~~~~~~~~~~
”’
Python 至始至终,同一时刻只有一个线程在工作,这是由于python 的GIL 超级大锁所决定的,它的作用就是保证同一时间只有一个线程在干活和执行权限,
哪个线程拿到GIL的执行资格,然后加了一个 RLock()锁,那么哪个线程就有执行彻底的资格,即使中途其他线程也拿到了GIL,也会处于阻塞状态并交出执行权,
一直等到RLock()的释放,其他线程才有运行的机会
”’
对比:在一个有超线程技术的双核四线程的处理器里,C++、Java可以做到一个物理核心并行两个线程,而Python始终只能运行一个线程,对CPU的极大浪费
第四:信号量
# 有一个能容纳五个人的房间,门外有五把钥匙,谁拿到其中的一把,谁就能进去。当第六个人也想进去的时候,门口没了钥匙,他只能此时等待和排队,
信号量也是如此,某些共享数据(比如全局变量),只能供给固定数目的线程使用
import threading,time class ThreadCust(threading.Thread): def __init__(self,n): threading.Thread.__init__(self) self.n = n def run(self): global var s.acquire() #2. 线程开始锁住 print ('var:',var) time.sleep(1) var -= 1 s.release() #3. 线程释放锁 var = 100 thre_list = [] s = threading.Semaphore(5) # 信号量,允许五个线程去操作共享的数据 for i in range(3): t = ThreadCust(i) t.start() t.join() print(var)
第五:Event(事件)
import threading,time class Teacher(threading.Thread): def run(self): print('今天的作业有:') print('作文一篇,800字') print('数学题60题') # event状态设置为True,当前线程会被阻塞,阻塞池的其他线程将被激活处于就绪状态,等待操作系统调度 event.set() time.sleep(3) print('开始交作业') event.set() class Student(threading.Thread): def run(self): # event状态为 Flase, 则阻塞当前线程,等待 event.set() 设置为 True 后,这边的线程才能被激活 event.wait() print('作业太多了,老师') time.sleep(3) # 恢复event 状态为 Flase event.clear() # 又进入 阻塞状态,等待其他线程的 event.set() event.wait() print('作业太多,做不完') event.clear() thread_list = [] event = threading.Event() tea = Teacher() tea.start() for i in range(3): stu = Student() stu.start() stu.join()
小结:
# event.wait() : 如果event的状态为 Flase, 将阻塞当前线程,默认值是False
# event.set() : 设置 event 的状态为 True, 所有阻塞池的线程被激活,进入就绪状态,等待操作系统调度
# event.isSet() : 返回event的状态值
# event.clear() : 恢复event的值为False
第6:队列queue
queue队列的方法 import queue # 队列种类 q1 = queue.Queue(maxsize=2) # 先进先出,参数为队列的长度,不写表示队列无限大 q2 = queue.LifoQueue(maxsize=3) # 先进后出,参数为队列的长度,不写表示队列无限大 q3 = queue.PriorityQueue(maxsize=3) # 存值 q1.put('jack') # 将值放入队列里 q1.put('tom') # 将值放入队列里 # 取值 q1.get()# 从队列取元素 print(q1.get())# 从队列取元素 # 其他方法 q1.qsize() # 返回队列大小 q1.empty() # 队列为空,返回True,否则 False q1.full() # 队列是否已满,是,返回True, 否,返回False q1.task_done() # 完成一项任务后,task_done() 向任务已经完成的队列发送一个信号 q1.join() # 等到队列为空,再执行别的操作
Queue
import queue q = queue.Queue() q.put('tom') q.put('jack') q.put('zhangsan') print(q.get()) # tom print(q.get()) # jack print(q.get()) # zhangsan
先进先出
后进先出
import queue q = queue.LifoQueue() q.put('tom') q.put('jack') q.put('zhangsan') print(q.get()) # zhangsan print(q.get()) # jack print(q.get()) # tom
后进先出,先进后出
import time, queue,threading,random q = queue.Queue() class QueueThread(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): while True: print('正在做包子') time.sleep(random.randrange(5)) bun = random.randrange(10) print('大厨[-%s-]做了编号[-%s-]的包子'%(self.name, bun)) q.put(bun) # 将包子压入队列 q.task_done() # 向正在 q.join()的线程发送信号:现在队列里有包子了,可以取了 class Person(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): while True: q.join() # 等候task_done() 的通知。有通知将执行下面代码 bun = q.get() print('食客[-%s-]吃了编号[-%s-]的包子'%(self.name,bun)) time.sleep(random.randrange(5)) qt = QueueThread('汉尼拔') per1 = Person('威尔') per2 = Person('史黛林') per3 = Person('斯大林') qt.start() per1.start() per2.start() per3.start()
生产者消费者
import time, queue,threading,random q1 = queue.Queue() q2 = queue.Queue() class QueueThread(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): while True: print('大厨[-%s-]正在等候客人点单' % self.name) q2.join() dish, guest = q2.get() print('大厨[-%s-]接收到客人[-%s-]点的[-%s-]' % (self.name, guest, dish)) q1.put(dish) # 厨师做好菜了,将菜放入q1队列 q1.task_done() # 通知菜已经做好了,可以取了 class Person(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): while True: lock.acquire() menu = ['宫保鸡丁', '糖醋排骨', '红烧牛肉', '大肠刺身'] # 定义菜单 dish = menu[random.randrange(4)] # 随机点菜 time.sleep(3) q2.put([dish, self.name]) # 将 客人 和 菜一起压入队列 q2.task_done() # 通知 q2 队列,有人点菜了 q1.join() # 等候厨师做菜,厨师做好后,往下执行 dish = q1.get() # 取菜 print('[-%s-]点的[-%s-]菜来了' % (self.name, dish)) if dish: lock.release() time.sleep(1) lock = threading.RLock() qt = QueueThread('汉尼拔') per1 = Person('威尔') per2 = Person('史黛林') per3 = Person('斯大林') qt.start() per1.start() per2.start() per3.start()
个性版生产者消费者
—恢复内容结束—