一、线程的创建
1. 使用threading模块创建线程(中阶的并发编程)(本章使用的方法)
2. 利用第三方模块(高阶并发编程)
涉及到threading模块创建线程分为两种模式:
(1)threading.Thread创建线程对象,指定两个参数 target,args。
(2) 继承threading.Thread,重写run方法创建线程对象。
方式一:
threading.Thread(target=,args=)
target=要执行的函数名(要使用多线程实现执行函数)
args=要执行函数的参数(以元组的形式传入)
time.sleep算是IO程序
import threading,time
def mission(end):
for i in range(end):
print(i)
time.sleep(0.5)
t1=threading.Thread(target=mission,args=(10,))
t2=threading.Thread(target=mission,args=(10,))
# 希望线程执行,需要将线程对象放到cpu的执行计划(执行列表)
# 对象.start
# start并不代表执行,只能代表将任务交给了cpu,cpu什么时候执行,由cpu说了算
t1.start()
t2.start()
方式二:继承threading.Thread
import threading,time
class MyThread(threading.Thread):
def run(self):
for i in range(10):
print(i)
# time.sleep(0.5)
# print(threading.current_thread())
# print(threading.get_ident())
# print(threading.main_thread())
t1=MyThread()
t2=MyThread()
t1.start()
t2.start()
run方法才是真正执行线程中任务的方法。如果直接调用run方法,那么相当于让当前任务串行执行。
t1.run()
t2.run()
二、线程的生命周期
1.新建:当新创建一个线程对象时,线程处于新建状态
2.就绪:执行start方法之后,线程处于就绪状态
3.运行:cpu将时间片分配给当前的线程对象,执行线程对应任务。
4.阻塞:因为某些条件没有满足,处于等待的过程中,cpu会将时间片分给其他的线程。比如sleep(1),当睡1s之后,只能代表当前线程被重新加入到cpu的执行列表中。并不代表会马上执行。
5.死亡:当线程run方法执行完毕,或者run方法中抛出了异常没有被捕获,程序意外终止。
三、线程的相关操作
1 threading.active_count() 当前活跃线程数量,(处于就绪之后,死亡之前)
print("当前活跃线程的数量",threading.active_count())
2.threading.enumerate() 返回一个列表,包含活跃的线程
li=threading.enumerate()
for i in li:
print(i)
3.threading.current_thread() 当前执行的线程
print(threading.current_thread())
4.threading.get_ident() 线程标志,一个序号,独一无二的序号
5.threading.main_thread() 返回执行解释器的线程(主线程)。对于一个进程来说,只有一个主线程。 if __name__=="__main__":
print(threading.main_thread())
6. start 就绪,加入到cpu的执行列表中,等待执行
7. run(),当线程获得时间片之后,会执行的方法
8. 线程对象.join(参数) 线程抢占。B.join(参数) 在A 线程中,如果调用了B线程的join方法,B线程抢占时间片
参数:抢占的时间,不写一直抢占时间片到B执行结束
def mission():
print("休眠开始")
time.sleep(2)
print("休眠结束")
t=threading.Thread(target=mission)
t.start()
t.join()
print("主线程执行")
例子:修路的例子
def mission():
print("修路开始")
print("修路过程中....")
time.sleep(2)
print("修路结束")
t=threading.Thread(target=mission)
print("start之前ident=",t.ident)
t.start()
print(t.is_alive())
print("start之后ident=",t.ident)
t.name="过马路的线程"
print("想要过马路")
t.join()
print("路修好了,可以过马路。。。。")
print(t.name)
print(t.is_alive())
9.name 线程中的一个私有属性,线程名字。get和set ,被property化
10.ident 线程标志 属性,get_ident ===ident 被propery化 。没有提供set方法,只有线程start启动之后,才有ident标志
11. is_alive() 判断是否存活
12.daemon 设置是否是守护线程(后台线程)
守护线程
有一个唱歌,乐队伴奏
两种情况
(1)将乐队伴奏设置为非守护线程(默认,前台线程)谁也不等谁(cpu采用异步模式对待每个线程)。唱歌的人唱完了,乐队伴奏如果还没有伴奏完毕,会继续伴奏
(2)将乐队线程设置为守护线程。唱歌的人唱完了,乐队伴奏不管有没有演奏完毕,都不会继续伴奏。如果将一个线程设置成守护线程:意味着告诉处理器,不用顾及当前的这个线程,当其他的非守护线程 退出的时候,守护线程也会跟着退出。
python中垃圾回收机制,使用守护线程
def music():
print("乐队线程开始执行")
time.sleep(1)
print("乐队持续在伴奏")
print("乐队持续在伴奏")
print("乐队持续在伴奏")
print("乐队持续在伴奏")
print("乐队持续在伴奏")
print("乐队结束")
if __name__=="__main__":
mt=threading.Thread(target=music)
# 守护线程需要在start之前设置
mt.setDaemon(True)
mt.start()
print("开始唱歌")
print("唱歌中...")
print("唱歌结束")
四、线程同步
解决:多线程中出现的资源共享问题。
1. 并发修改出现问题
票就是共享资源
ticket=100
def buy_ticket():
global ticket
while ticket>0:
time.sleep(0.5)
print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
2. 线程锁
希望在同一个时间点内,一片共享资源只希望被一个线程访问。
方式:lock=threading.Lock()
加锁:lock.acquire()
解锁:lock.release()
第一次解决: while循环可能同时进入了其他两个线程
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while ticket>0:
lock.acquire()
# time.sleep(0.5)
print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第二次 问题原因:张三进入之后加锁,循环将所有的排票都抢完毕
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
lock.acquire()
while ticket>0:
# time.sleep(0.5)
print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第三次: 没有人解最后锁
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while True:
lock.acquire()
if ticket>0:
# time.sleep(1)
print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
else:
break
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第四次
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while True:
try:
lock.acquire()
if ticket>0:
time.sleep(0.2)
print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
else:
break
finally:
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
五、死锁
死锁的定义:当两个或者多个线程同时拥有自己的资源,又互相等待对方的资源,导致程序永远先入僵持状态
共有两把锁
A----锁1-----希望拥有锁2
B----锁2-----希望拥有锁1
多线程并发访问数据的时候,要在共享资源上加锁。如果加的锁不只一把,可能会出现死锁
import threading,time
lock1=threading.Lock()
lock2=threading.Lock()
def mission(l1,l2): #l1,l2代表传入的两把锁
l1.acquire()
print("{}获得了{}".format(threading.current_thread().name,id(l1)))
time.sleep(1)
l2.acquire()
l1.release()
l2.release()
a=threading.Thread(target=mission,args=(lock1,lock2))
b=threading.Thread(target=mission,args=(lock2,lock1))
a.start()
b.start()
六、通知和等待
抢票,一个资源(生产者),多个线程共享。
生产者和消费者。
使用from threading import Condition 的锁,不仅有获取和释放的方法
还有:
wait :等待, 会释放当前线程占用的锁(跟time.sleep不一样,sleep,不释放锁)
notifyall:通知所有等待的线程加入cpu执行列表
notify方法:任选一个等待线程,加入cpu执行列表
需求:
生产者:生产商品,让商品+1,定义一个列表仓库,每次列表中加入一个元素,当做生产一个商品
消费者:消费商品,让商品-1,让列表中元素被删除
当供过于求:生产者生产太快,商品在仓库中达到3件之后,生产者不能再生产,需要被阻塞。被唤醒的时机:只有要商品被消费了
求大于供:消费者消费太快,商品在仓库中0个,消费者就不能再消费,需要被阻塞。被唤醒的时机:只要有商品被生产了
from threading import Condition
lock=threading.Condition()
def produce(li):
i=0
while True:
try:
lock.acquire()
if len(li)==3:
print("仓库已满,生产阻塞")
lock.wait()
else:
li.append("商品{}".format(i))
i+=1
print("生产了{}商品".format(i))
lock.notify_all()
finally:
lock.release()
def consume(li):
while True:
try:
lock.acquire()
if len(li)==0:
print("仓库已空,消费阻塞")
lock.wait()
else:
print("消费了{}商品".format(li.pop(0)))
lock.notify_all()
finally:
lock.release()
li=[]
t1=threading.Thread(target=produce,args=(li,))
t2=threading.Thread(target=consume,args=(li,))
t1.start()
t2.start()
七、队列(线程)
队列数据类型,内部实现了锁的机制,队列多用于多线程的并发
队列分为三种:
①先进先出队列:队列
②先进后出队列:堆栈
③优先队列:按照优先级出队列
(1) 先进先出队列
queue.Queue(size):size=0或者负数,表示无限容量,如果size有值,代表最大容量
q=queue.Queue(3)
通过q.qsize() 返回队列中元素的格式
print(q.qsize())
q.empty队列是否为空
print(q.empty())
q.full() 判断队列是否已满
print(q.full())
q.put向队列中添加元素
item:要添加的元素
block:继续向队列中添加元素的时候,如果队列已满,put方法是否是处于阻塞状态(默认True)block如果=False,当队列已满的时候 ,再继续添加元素,则会报错。
timeout:队列已满,如果在指定的时间内(单位:秒),仍然无法添加元素,则会产生异常
q.put("hello")
q.put("world",block=False)
put_nowait 代表只要队列已满,put函数执行的时候会报错。
q.put_nowait(item) # 等价于put(item,block=False)
q.get() 向外取元素(规则就是先进先出)
q.put("python")
print(q.get())
print(q.get())
print(q.get())
print(q.get())
如果队列是空队列,继续向外get元素,get方法是默认的阻塞函数。用法跟put中一样。
q.get(block=False)
q.get_nowait()====q.get(block=False)
(2) 先进后出,堆栈
lq=queue.LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())
print(lq.get())
while not q.empty():
print(q.get())
(3) 优先队列
不是按照传统队列的先进先出,或者后进先出,而是根据队列的优先级别进行排列。进的时候,正常进入 ,出的时候是按照优先级别出。优先级队列中的元素必须支持元素之间的比较。
print("abc"<"bcd")
print((1,2,4)<(3,4))
q=queue.PriorityQueue()
q.put("clock")
q.put("banana")
q.put("egg")
q.put("apple")
while not q.empty():
print(q.get())
q.put((1,2,3))
q.put((2,2,3))
q.put((3,2,3))
q.put((-1,2,3))
while not q.empty():
print(q.get())
q=queue.PriorityQueue()
q.put((2,"clock"))
q.put((1,"banana"))
q.put((3,"egg"))
q.put((4,"apple"))
# q.put("a")
while not q.empty():
print(q.get())
应用队列实现生产者和消费者的例子
import queue
import threading
def produce(q):
i=1
while True:
q.put(i)
print("生产商品{}".format(i))
i+=1
time.sleep(0.5)
def consume(q,name):
while True:
print("{}消费了{}".format(name,q.get()))
time.sleep(0.1)
# 队列本身创建的时候就有容量的设置
q=queue.Queue(3)
t1=threading.Thread(target=produce,args=(q,))
t2=threading.Thread(target=consume,args=(q,"tom"))
t3=threading.Thread(target=consume,args=(q,"jerry"))
t1.start()
t2.start()
t3.start()