文章目录
前言
并发和其表现形式之一并行处理是一个广泛而有复杂的话题。如果两个事件互不影响,可以同时并行的处理则两个事件是并发的。本文将讲述在python中最重要的三个并发模型:多线程、多进程、异步编程。
一、多线程
线程:调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
threading模块
threading模块支持多线程编写的重要模块。此模块提供了Thread、Lock、RLock、Condition、Event、Timer和Semaphore等类来支持多线程编写。其中最基本的类为Thread,可以通过该类创建线程并控制线程运行。
创建线程的两种方式:
- 为.Thread类传递运行函数。
- 继承Thread并在派生类中重写构造方法和run方法。
import time
def countdown(n):
while n>0:
print("T-minus",n)
n-=1
time.sleep(1)
from threading import Thread
t=Thread(target=countdown,args=(10,))
t.start()
关于Thread对象的一些说明:
t=Thread(target=countdown,args=(10,),daemon=False,)
target接收函数名,args:函数参数,以元组的形式传递,daemon:是否为守护线程,默认为False。
当创建一个线程实例时,在调用它的start()方法之前,线程不会立即执行。线程开始运行之后可以使用t.is_alive()来判断线程是否在运行。用上面的方法运行线程主线程并没有等待自定义线程运行完毕,而是自己运行完成,然后子线程再完成。如果我们需要子线程运行完毕之后主线程再运行完毕可以使用t.join([timeout])
t=Thread(target=countdown,args=(10,))
start1=time.time()
t.start()
t.join()
end=time.time()
print(end-start1)# 10.097468376159668
谈到这我们依然在某个点停止线程,如果我们希望这么做的话,可以考虑使用第二种创建线程的方法。
import time
class CountdownTask():
def __init__(self):
self.running=True
def terminate(self):
self.running=False
def run(self,n):
while self.running and n>0:
print('T-minus',n)
n-=1
time.sleep(1)
c=CountdownTask()
from threading import Thread
t=Thread(target=c.run,args=(10,))
t.start()
time.sleep(5)
c.terminate()
说明:t.start()自动调用run()方法,启动线程,执行线程代码
协程同步技术
多个线程会同步相互协作地完成同一件任务。但线程的一个关键特性是每个线程都是独立运行且状态不可预测, 共享资源会被随机的破坏掉,已及产生我们称之为竞态条件的奇怪行为。这意味着必须保护共享数据。前面说的Lock、RLock、Condition、Event、Timer和Semaphore均是为此准备的。
显式加锁、隐式加锁
在介绍Lock等类之前我们先介绍两种不同的加锁方式。
import threading
class SharedCounter:
def __init__(self,inital_value=0):
self._value=inital_value
self._value_lock=threading.Lock()
def incr(self,delata=1):
self._value_lock.acquire()
self._value+=delata
self._value_lock.release()
def decr(self,delata=1):
with self._value_lock:
self._value-=delata
不管是incr还是decr中都有加锁,不过加锁的方式不同,前者是显式使用锁,后者是隐式,后者更加优雅,也不容易出错。
RLock、Lock
Lock是比较低价的同步原语,当被锁定以后不属于特定的线程,其提供了两种方法acquire()它锁定锁的执行并阻塞锁的执行,直到其他线程调用release()方法将其设置为解锁状态。
RLock可重入锁是一种常用的线程同步语句。,即使被同一个线程acquire多次也不会被阻塞。【而lock会,这也是二者最大的区别】
在这里插入代码片import threading
lock1 = threading.Lock()
lock1.acquire()
print(lock1)
lock1.acquire()
print(lock1) #无法打印
#-----------------------------------
# lock = threading.RLock()
# lock.acquire()
# print(lock)
# lock.acquire()
# print(lock)#可以打印
Lock与RLock的使用示例
import threading
class SharedCounter:
_lock=threading.RLock()
def __init__(self,inital_value=0):
self._value=inital_value
def decr(self,delata=1):
with SharedCounter._lock:
self._value-=delata
#--------------RLock
class SharedCounter:
_lock=threading.RLock()
def __init__(self,inital_value=0):
self._value=inital_value
def decr(self,delata=1):
with SharedCounter._lock:
self._value-=delata
Event对象
如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。
from threading import Thread,Event
import time
def countdown(n,started_evt):
print('开始啦')
started_evt.set()
while n>0:
print("T-minus",n)
n-=1
time.sleep(1)
started_evt=Event()
t=Thread(target=countdown,args=(10,started_evt))
t.start()
started_evt.wait()
print('countdown running')
对Event几种方法说明:
- event.isSet():返回event的状态值;
- event.wait():如果 event.isSet()==False将阻塞线程;
- event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
- event.clear():恢复event的状态值为False。
Semaphore
Event会唤醒所有等待对象,如果我们编写的程序只希望唤醒一个单独的等待线程,那么最好使用Semaphore
多线程同时运行,能提高程序的运行效率,但是并非线程越多越好,而semaphore信号量可以通过内置计数器来控制同时运行线程的数量,启动线程(消耗信号量)内置计数器会自动减一,线程结束(释放信号量)内置计数器会自动加一;内置计数器为零,启动线程会阻塞,直到有本线程结束或者其他线程结束为止。
semaphore信号量相关函数介绍
- acquire() 消耗信号量,内置计数器减一;
- release() 释放信号量,内置计数器加一;
在semaphore信号量有一个内置计数器,控制线程的数量,acquire()会消耗信号量,计数器会自动减一;release()会释放信号量,计数器会自动加一;当计数器为零时,acquire()调用被阻塞,直到release()释放信号量为止。
import threading
# 导入时间模块
import time
# 添加一个计数器,最大并发线程数量5(最多同时运行5个线程)
semaphore = threading.Semaphore(5)
def foo():
semaphore.acquire() # 计数器获得锁
time.sleep(2) # 程序休眠2秒
print("当前时间:", time.ctime()) # 打印当前系统时间
semaphore.release() # 计数器释放锁
thread_list = list()
for i in range(20):
t = threading.Thread(target=foo, args=()) # 创建线程
t.start() # 启动线程
thread_list.append(t)
for t in thread_list:
t.join()
print("程序结束!")
queue对象
有多个线程,我们想要在这些线程之间实现安全的通信或数据交换,最安全的做法使用queue模块中的Queue(队列),首先创建一个Queue实例,它会被所有线程共享,通过put添加、get获取来给队列中添加元素。
from queue import Queue
from threading import Thread
def producer(out_q,n)