进程是独立的空间,在一个进程里面的线程,资源是共享的,但是有的线程空间有限,只能容纳2个人,这种情况下,线程就会有锁,互斥锁,有人释放锁后,其他人才能进去这个线程空间执行任务
单核cpu:出现假死机(转个不停) cpu是乱序执行的
单核cpu:多个任务来回快速切换,每个任务执行一个时间片,并不是同时执行
四核cpu:只执行四个任务,理想情况下可以做到同时执行四个任务(实际情况无法做到,任务不止4个)
假设执行多个任务:情况和单核一样,多个任务快速切换,但是效率比单核高(切换的时间轮更小)
线程/进程
**进程: **进程指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。进程是操作系统分配资源的最小单位。进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
**线程: **为进程中的一个执行流程,一个进程中可以运行多个线程。 线程是进程中任务执行的最小单位,线程是系统独立调度和分派CPU的基本单位。同一进程中的线程共享内存空间。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程)。
多进程文章
由于每次创建线程的时候就要需要时间申请内存空间,如在次使用的时候,还要在去创建,这个过程还要花时间,所以不如把线程创建好之后,在把线程放起来,等在用的时候就不用去创建线程,就不用再花这个创建线程的过程的时间,这就是线程池
要进入__main__
主函数入口才能创建进程实例,执行进程
freeze_support()
:在window,保证把代码转成.exe
然后执行,支持程序生成的exe可执行程序正常运行,在__main__
主入口调用
系统在执行的任务,每个任务就是一个进程,每一个进程至少有一个线程(主线程)(主线程和子线程)
进程
:系统分配资源的最小单位线程
:线程是在进程中任务执行的最小单位,线程是系统独立调度和分派CPU的基本单位(对于单核CPU
,执行多任务,分时复用,不同的时间点切换任务执行,就在每个不同的时间片执行每个任务)
#python中由multiprocessing模块提供进程服务。
from multiprocessing import Process
from multiprocessing import Pool
多线程文章
由于每次创建线程的时候就要需要时间申请内存空间,如在次使用的时候,还要在去创建,这个过程还要花时间,所以不如把线程创建好之后,在把线程放起来,等在用的时候就不用去创建线程,就不用再花这个创建线程的过程的时间,这就是线程池
线程的生命周期,新建 (启动) 就绪 (调度) 运行 阻塞(满足阻塞条件)或者(结束)死亡
每个进程的主线程和子线程,python线程提供了两个模块,高级模块threading和低级模块_thread,threading对_thread进行了封装
import threading
import _thread
from concurrent.futures import ThreadPoolExecutor # 线程池
win
是没有的(很难实现),只是在linux
和unix
下才有
当前进程完全复制–>生成子线程,也会复制内存的数据,但是地址空间不同 (分叉就是复制,镜像),子进程就是主进程的镜像,彼此是独立的,执行顺序是随机的
fork()
函数:Unix/Linux
操作系统提供了一个fork()系统调用,调用一次返回两次(主进程和子进程),因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。- 子进程永远返回0, 父进程返回子进程的ID,这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用
getppid()
就可以拿到父进程的ID- 用分叉技术,就可以完全绕开
GIL锁(全局解析器锁)
,效率变高,占用了更多的内存空间
在unix、linux 通过系统函数**fork()**从现在的进程产生另一个进程,不能再win上用- Python的
os模块
封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程
下面的代码是无法在Windows上运行的,执行在unix、linux、max系统上面运行
有了fork
调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache
服务器就是由父进程监听端口,每当有新的http
请求时,就fork
出子进程来处理新的http
请求
import os
print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print('子进程是 (%s) 主进程是 %s.' % (os.getpid(), os.getppid()))
else:
print(' (%s) 创建子进程 (%s).' % (os.getpid(), pid))
os.fork() #返回两次,在子进程内返回0,在父进程内返回子进程的id
os.getpid() #返回当前进程id
os.getppid() #返回父进程的id
运行结果如下:
Process (876) start...
(876) 创建子进程 (877).
子进程为 (877) 父进程为 876.
在Unix/Linux下,multiprocessing
模块封装了fork()
调用,使我们不需要关注fork()
的细节。由于Windows没有fork调用,因此,multiprocessing
需要“模拟”出fork
的效果,父进程所有Python对象都必须通过pickle
序列化再传到子进程去,所以,如果multiprocessing
在Windows下调用失败了,要先考虑是不是pickle
失败了。
在Unix/Linux下,可以使用fork()调用实现多进程,要实现跨平台的多进程,可以使用multiprocessing模块。
from multiprocessing
:python实现多进程的模块,可在win开发,部署到linux(服务器基本是linux),产生跨平台进程
from multiprocessing import Process
: 进程对象,在主进程(当前代码)构造子进程,个人理解是在新建的进程还没执行完毕之前,主线程处于半阻塞
状态
进程实现:(若很很多的线程的时候,不要使用,用进程池)
守护进程
:后台进程,daemon
参数为True
的是就是守护进程,表示主进程结束后,子进程(守护进程)必须结束,随主进程的退出而退出
from multiprocessing import Process
import os,time
#在现在的进程产生一个进程,下面的函数就是新进程做的任务
def run():
#获取进程id
print('子进程id是%s'%os.getpid())#7216
time.sleep(3)
#新的产生的进程运行的入口一定要在 __main__ 里面执行
#在其他地方执行,如不做特殊处理会报错
if __name__ == '__main__':
print('主进程id是%s'%os.getpid())
#将子进程对象传递给Process构造子进程
new_process = Process(target=run,args=(),daemon=True)
#daemon=True表示这个子进程是主进程的守护进程
new_process.start() #创建新的进程,主进程处于半阻塞状态,要等新进程执行结束,主进程才结束
new_process.join()
#进程等待,主进程执行完,在等子进程执行完,然后主进程才退出
#即进程同步
#如果不加join主进程结束了 子进程还没结束这种情况就是阻塞 内存空间不能得到释放
new_process.close()#关闭这个进程
进程实现总结:
Process(target=run_proc, args=('test',))
用于创建一个子进程,target
指定子进程中任务,args
给target
指定的任务传参。start()
方法启动进程join()
方法等待子进程结束后继续往下运行,用于进程间同步。Process(target=run_proc, args=('test',)daemon=True)
:daemon=True
守护进程后台进程,子进程随主进程的退出而退出。
进程池:可指定最大进程数目,同时在执行进程,有人先执行完,就把进程放回进程池,就有了空闲进程,后面补上
上述情况就是进程的复用
from multiprocessing import Process,Pool #pool进程池对象
multiprocessing.cpu_count #返回一个虚拟cpu,即逻辑处理器,逻辑核心数
import os,time
def run(num):
time.sleep(num)
print(num**3)#pow(num,3)也是求num的3次方
if __name__ == '__main__':
#主进程
#构造池对象,最大进程数为8个,或者用cpu_count() 返回cpu的核数
pool = Pool(8)
for i in range(9):
#添加子进程,传入子线程对象,和args()
#构造子进程,传递子进程函数对象的参数,是一个元组
#apply_async异步非阻塞,如不json的情况下,主进程结束,其他进程也结束
#pool.apply(...)阻塞的,要等创建的进程结束,主进程才结束
#在py3中apply也是调用apply_async异步提交任务
pool.apply_async(run,args=(i,))
#当关闭线程池(构造完指定的进程后)的时候,不在接受新的进程
pool.close()#停止往进程池中送数据
pool.terminate()# 杀死终止所有的进程
pool.join()#等待所有的任务都执行完,就结束所有进程
如上面进程池有8个进程,但是创建了9个新的进程,所以第9个进程要等第1个执行完后才开始执行,实际上可以不指定进程池的进程数,这个最大的数量就跟当前机器的逻辑处理器有关
总结
1.
Pool(n)
:产生大小为n的进程池,Pool
的默认大小是CPU的核数
2.pool.close()
:把进程池状态改为close,不允许再添加新的任务,但是已经执行了的进程还会继续执行
3.pool.join()
:父进程等待所有子进程执行完毕,在join之前需调用close()
方法
4.pool.apply(target=func,args=(x,))
:方法创建的进程是阻塞的,先执行子进程,等待子进程结束后在往下执行
5.pool.apply_async(target=func,args=(x,))
:创建的进程是异步非阻塞的。意思就是:不用等待当前进程执行完毕,随时根据系统调度来进行进程切换,如果不join
的情况下,主进程结束了,其他还没执行新创建的进程也跟着结束
6.pool.terminate()
:关闭进程池,强制终止进程池,已经开始的子进程全部被干掉了
获取当前机器的cpu核心数
import psutil #要安装
psutil.cpu_count()#返回逻辑CPU核心数,逻辑处理器数量
psutil.cpu_count(logical=Fasle)#返回真实的核心数
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess
模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
下面的例子演示了如何在Python代码中运行命令nslookup www.python.org
,这和命令行直接运行的效果是一样的:
import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)
运行结果:
$ nslookup www.python.org
Server: 192.168.19.4
Address: 192.168.19.4
#53Non-authoritative answer:
www.python.org canonical name = python.map.fastly.net.
Name: python.map.fastly.net
Address: 199.27.79.223Exit code: 0
如果子进程还需要输入,则可以通过communicate()
方法输入
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
#上面的代码相当于在命令行执行命令nslookup,然后手动输入:
set q=mx
python.org
exit
进程间的通讯,因为进程间是彼此独立的,每个进程都有自已的内存空间,每个进程之间的执行是相互独立的
Process
之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing
模块包装了底层的机制,提供了Queue、Pipes
等多种方式来交换数据
队列机制通讯(中转站进行交互),管道机制通讯(双方通了,可直接沟通),Manager管道(分布式处理,对于分布式进程)
from multiprocessing import Manager
下面是Queue
,线程也可以通过下面的方法实现Queue队列的通讯,不能直接通过None或者长度取判断队列的元素是否取完,这样是线程不安全的,直接去取,等待队列没元素,抛异常,把异常捕获就行
from multiprocessing import Precess,Queue #Queue是进程队列,把构造完的进程放进Queue对象
from multiprocessing import Process,Queue
import time
#往进程队伍添加元素
sign_done = '__flag_done'
def write(queue):
for num in range(8):
#往Queue队列添加元素
queue.put(num)
print('添加了%s'%num)
time.sleep(0.1)
queue.put(sign_done)#表示写完了,一个指令
def read(queue):
while 1:
#从Queue队列取元素
num = queue.get(True)
time.sleep(0.6)
print('获取了%s'%num)
if num == '__flag_done':
break
print('down')
#构造连个进程,添加和获取
if __name__ == '__main__':
#构造队列对象
queue = Queue()
#构造子进程
w_process = Process(target=write,args=(queue,))
r_process = Process(target=read,args=(queue,))
w_process.start()
r_process.start()
w_process.join()
r_process.terminate()#关闭读的进程,一旦执行,就关闭进程(如果没执行完的进程全部干掉)
print('end')
#进程之间通过Queue的进程队列实现通讯,数据的交互与通讯
小结
进程间通信是通过
Queue、Pipes
等实现的,个人理解Queue
相当于一个容器,两个进程通过一个第三方的容器(队列)平台进行数据的交互通讯。
threading模块
Thread
:线程类,这是我们用的最多的一个类,可以指定线程函数执行或者继承自它都可以实现子线程功能。
Timer
:与Thread类似,但要等待一段时间后才开始运行。
Lock
:锁原语,这个我们可以对全局变量互斥时使用,在使用线程处理贡献数据的时候使用。
RLock
:可重入锁,使单线程可以再次获得已经获得的锁。
Condition
:条件变量,能让一个线程停下来,等待其他线程满足某个“条件”。
Event
:通用的条件变量,多个线程可以等待某个事件发生,在事件发生后,所有的线程都被激活,可用于实现线程间的通讯。
Semaphore
:为等待锁的线程提供一个类似“等候室”的结构。
BoundedSemaphore
:与semaphore类似,但不允许超过初始值。
Queue
:实现了多生产者(Producer)、多消费者(Consumer)的队列,支持锁原语,能够在多个线程之间提供很好的同步支持。
Thread类:是你主要的线程类,可以创建进程实例。该类提供的函数包括:
getName(self)
:返回线程的名字
isAlive(self)
:布尔标志,返回线程状态,判断线程是否还在运行中
isDaemon(self)
: 返回线程的daemon标志,判断是否是守护线程
join(self, timeout=None)
:程序挂起,直到线程结束,如果给出timeout
,则最多阻塞timeout
秒
run(self)
:定义线程的功能函数
setDaemon(self, daemonic)
:把线程的daemon标志设为daemonic
setName(self, name)
:设置线程的名字
start(self)
:开始线程执行
Queue提供的类
Queue
:队列
LifoQueue
:后入先出(LIFO)队列
PriorityQueue
:优先队列
Python这门解释性语言也有专门的线程模型,Python虚拟机使用GIL(Global Interpreter Lock,全局解释器锁)
来互斥线程对共享资源的访问,但暂时无法利用多处理器的优势,GIL只有cpython
有
线程的执行是由cpu调度执行的
多线程:一个进程有多个线程(主线程和多个子线程),线程就是操作系统的执行单元
进程:多个线程(主线程和多个子线程)
线程构造:用法和进程一样
低级线程模块 _thread一般很少用
#调用_thread模块中的start_new_thread() 函数来产生新线程
#语法如下:_thread.start_new_thread ( function, args[, kwargs] )
import _thread # 低级模块
import random, time
def func_a(a, b):
tid = _thread.get_ident()
for i in range(5):
rnum = random.randint(0, 10)
time.sleep(0.03)
print('Thread:%d,generate a random num %d' % (tid, rnum))
return a + b
t1 = _thread.start_new_thread(func_a, (1, 2))
t2 = _thread.start_new_thread(func_a, (3, 2))
#设置死循环让主线程不结束
while True:
pass
总结
1.
function
- 线程函数。
2.args
- 传递给线程函数的参数,他必须是个tuple类型。
3.kwargs
- 可选参数
高级线程模块:threading
高级模块,封装了_thread
,_thread是低级模块
thread
和 threading
这两个模块来实现的,其中Python的threading
模块是对thread
做了一些包装的,可以更加方便的被使用,所以我们使用threading
模块实现多线程编程Thread
对象里,让它来执行;另一种是直接从Thread
继承,创建一个新的class
,把线程执行的代码放到这个新的 class里from threading import Thread
threading.current_thread() #获取当前线程
def funa(num):
for n in range(int(num)):
print(" 线程名为 %s, num: %s" % (threading.currentThread().getName(), n))
#for循环构建多个线程
def func(thread_num):
thread_list = list()
for i in range(0, thread_num):
thread_name = "thread_%s" % i
new_thread = Thread(target=funa,args=(num,),name=thread_name , daemon=True)
thread_list.append(new_thread )#循环产生多个线程
for thread_1 in thread_list:
thread_1.start()
for thread_2 in thread_list:
thread_2.join()
if __name__ == "__main__":
func(3)
总结
1.
threading.Thread(target,name,daemon,args,kwargs)
:创建一个线程.target
指定线程任务,name
线程名,daemon
为True
是守护线程,args,kwargs
为子线程中任务函数所需参数.
2.start()
:开启线程
3.json()
:等待线程结束,然后往下执行
4.由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading
模块有个current_thread()
函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread
,子线程的名字在创建时通过name
参数指定,名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
5.线程的属性p = threading.current_thread()
- 属性:
p.name
线程名、p.ident
线程id、p.daemon
是否为守护线程- 方法:
p.getName()
获取线程名、p.is_alive()
获取线程状态、p.isDaemon()
判断是否是守护进程
6.
threading
中有用的几个方法
threading.currentThread()
: 返回当前的线程变量。threading.enumerate()
:返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount()
:返回正在运行的线程数量,与len(threading.enumerate())
有相同的结果
多线程
from multiprocessing.dummy import Pool
#线程池,在dummy子模块下面
#进程池和线程池只是池对象不同,池对象在模块不同的位置
import threading
import os
def func(a):
print(f'{a}线程:%s'%threading.current_thread().getName())
if __name__ == '__main__':
pool = Pool()
thread_list = [pool.apply_async(func=func,args=(i,)) for i in range(5)]
print('start')
pool.close()
pool.join()
print('end')
#3个线程池的创建
from multiprocessing.dummy import Pool
from multiprocessing.poolimport ThreadPool#返回值拿不到
#有异步机制,也有进程池,可以通过result获取返回值
from concurrent.futures import ThreadPoolExecutor,ProcessPlloExecutor
import threading
import os
def func(a):
print(f'{a}线程:%s'%threading.current_thread().getName())
pool = ThreadPoolExecutor(3)
thread_list = [pool.submit(func,i) for i in range(5)]
for i in thread_list:
i.result()
import threading
import os
# 自定义类继承自Thread类
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
#设置当前线程的名称
self.setName("new" + self.name)
#线程执行的方法
def run(self):
print(f'{threading.current_thread().getName()}线程id为{os.getpid()}')
#join方法原型如下,这个方法是用来阻塞当前上下文,直至该线程运行结束
def join(self,timeout=None):
pass
#将进程设置收守护进程的方法
def setDaemon(self):
pass
if __name__ == "__main__":
for thread in range(0, 5):
t = MyThread()
t.start()
守护线程 setDaemon方法
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是,只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon
方法,并设置其参数为True
。
多个线程操作共享数据,通过Lock锁对象,避免多个线程操作共享数据
同一进程中的线程共享 进程内存空间. 对于同一变量或者资源都可以进行操作
线程之间共享数据最大的危险在于多个线程同时使用资源导致资源抢占,或者数据读写结果混乱的问题,问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全
多个线程操作共享数据出现了混乱,循环次数越大,混乱越高
通过锁来控制,如线程1获取到锁对象,那么线程1就操作money,线程2就无法进入,因为线程2没有获取到锁
要等线程1操作完money,释放锁后,线程2获取锁才能操作money(有锁的情况下,只有一个线程操作共享数据)
互斥锁:来保证共享数据操作的完整性,每个对象都对应于一个可称为互斥锁的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象,python中的Lock()
类就是互斥锁
加锁Lock()
,先创建锁对象,然后在操作共享数据之前加锁
from threading import Thread,Lock,current_thread
money = 0
#构造锁对象
lock = Lock()
def change_data(num):#次数
#在局部范围内操作全局
global money
#current_thread().name-->获取当前线程名
print('当前线程是%s'%current_thread().name)
try:
#激活Lock对象,加锁,一定要在操作共享数据前加锁
lock.acquire()
for i in range(num):
money +=num
money -=num
finally:
#释放锁,一定要在操作完之后
lock.release()
print('action--->',money)
#可以用上下文的形式
with lock:
for i in range(num):
money +=num
money -=num
#定义两个线程:存钱取钱、存钱取钱
t1 = threading.Thread(target=change_data, args=(999999,))
t2 = threading.Thread(target=change_data, args=(999999,))
#每次执行都是存,在取,理想的结果是0
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
#互斥锁:表示当前资源在被使用的时候,其他不能对此资源进行操作
同步阻塞
当一个线程调用
Lock
对象的acquire()
方法获得锁时,这把锁就进入locked状态。因为每次只有一个线程1可以获得锁,所以如果此时另一个线程2试图获得这个锁,该线程2就会变为block同步阻塞状态。直到拥有锁的线程1调用锁的release()
方法释放锁之后,该锁进入unlocked状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
总结
Lock()
产生一个锁
RLock()
: 可重入锁,使单线程可以再次获得已经获得的锁
lock.acquire()
:申请使用锁,获得锁,上锁
lock.release()
:释放锁
更好的方式 with 语句上下文,类似文件操作with open()
通过对公共资源使用互斥锁,这样就简单的到达了我们的目的,但是如果我们又遇到下面的情况:
1、遇到锁嵌套的情况该怎么办,这个嵌套是指当我一个线程在获取临界资源时,又需要再次获取;
2、如果有多个公共资源,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源;
上述这两种情况会直接造成程序挂起,即死锁,下面我们会谈死锁及可重入锁RLock
死锁概念
所谓死锁: 是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
如下,代码中展示了一个线程的两个功能函数分别在获取了一个竞争资源之后再次获取另外的竞争资源,有A,B两块资源,线程1获取到了A资源的锁,在不释放A锁的情况下去获取B资源的锁,但是线程2获取了B资源的锁,在不释放B锁的情况下去获取A资源的锁,然后两个线程都在等待对方的资源,在没有外力的作用下造成了阻塞,形成了死锁
竞争资源死锁
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.test1()
self.test2()
def test1(self):
if lock1.acquire():
print(f'{threading.current_thread().getName()}获取锁1')
if lock2.acquire():
print(f'{threading.current_thread().getName()}获取锁2')
lock2.release()
lock1.release()
def test2(self):
if lock2.acquire():
print(f'{threading.current_thread().getName()}获取锁2')
if lock1.acquire():
print(f'{threading.current_thread().getName()}获取锁1')
lock1.release()
lock2.release()
if __name__ == '__main__':
for i in range(10):
t1 = MyThread()
t1.start()
避免死锁
按照顺序获取锁,如大的顺序锁被获取了,那么小的获取锁就不给获取
死锁狗,找死锁,然后去按照顺序去释放锁
避免死锁主要方法就是:正确有序的分配资源,避免死锁算法中最有代表性的算法是Dijkstra E.W 于1968年提出的银行家算法
可重入锁RLock
考虑这种情况:如果一个线程遇到锁嵌套的情况该怎么办,这个嵌套是指当我一个线程在获取临界资源时,又需要再次获取
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁:threading.RLock
。这个RLock
内部维护着一个Lock
和一个counter
变量,counter
记录了acquire
的次数,从而使得资源可以被多次require
。直到一个线程所有的acquire
都被release
,其他的线程才能获得资源
嵌套死锁,换成Rlock就不会死锁
import threading
import time
counter = 0
#mutex = threading.RLock() #不会死锁
mutex = threading.Lock() #会死锁
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global counter, mutex
time.sleep(1);
if mutex.acquire():
counter += 1
print "I am %s, set counter:%s" % (self.name, counter)
if mutex.acquire():
counter += 1
print "I am %s, set counter:%s" % (self.name, counter)
mutex.release()
mutex.release()
if __name__ == "__main__":
for i in range(0, 200):
my_thread = MyThread()
my_thread.start()
如何防止死锁—有关死锁的文章
你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题
当两个线程相互等待对方释放“锁”时就会发生死锁,或者嵌套申请互斥锁时会产生死锁
举例:
如何解决?
1.尽可能保证每一个 线程只能同时保持一个锁
2.常用的死锁检测与恢复的方案是引入看门狗计数器
3.给锁进行排序,比如id排序,获取锁严格按照从小到大的顺序获取,释放反之
使用Condition实现复杂同步
Condition
被称为条件变量,除了提供与Lock类似的acquire
和release
方法外,还提供了wait
和notify
方法
使用Condition的主要方式为:线程首先acquire
一个条件变量,然后判断一些条件。如果条件不满足则wait
;如果条件满足,进行一些处理改变条件后,通过notify
方法通知其他线程,其他处于wait
状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题
生产者-消费者模型来来演示在Python中使用Condition
实现复杂同步
import threading
import time
condition = threading.Condition()
products = 0
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global condition, products
while True:
if condition.acquire():
if products < 10:
products += 1;
print "Producer(%s):deliver one, now products:%s" %(self.name, products)
condition.notify()
else:
print "Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products)
condition.wait()
condition.release()
time.sleep(2)
class Consumer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global condition, products
while True:
if condition.acquire():
if products > 1:
products -= 1
print "Consumer(%s):consume one, now products:%s" %(self.name, products)
condition.notify()
else:
print "Consumer(%s):only 1, stop consume, products:%s" %(self.name, products)
condition.wait();
condition.release()
time.sleep(2)
if __name__ == "__main__":
for p in range(0, 2):
p = Producer()
p.start()
for c in range(0, 10):
c = Consumer()
c.start()
代码中主要实现了生产者和消费者线程,双方将会围绕products来产生同步问题,首先是2个生成者生产products ,而接下来的10个消费者将会消耗products
另外:
Condition
对象的构造函数可以接受一个Lock/RLock
对象作为参数,如果没有指定,则Condition
对象会在内部自行创建一个RLock
;除了notify
方法外,Condition
对象还提供了notifyAll
方法,可以通知waiting
池中的所有线程尝试acquire
内部锁。由于上述机制,处于waiting
状态的线程只能通过notify
方法唤醒,所以notifyAll
的作用在于防止有线程永远处于沉默状态
使用Event实现线程间通信
上面使用Condition
对象初步实现了线程间的通讯, 但是更通用的用法就用threading.Event
threading.Event
:可以使一个线程等待其他线程的通知,我们把这个Event
传递到线程对象中,Event
默认内置了一个标志,初始值为False
。一旦该线程通过wait()
方法进入等待状态,直到另一个线程调用该Event
的set()
方法将内置标志设置为True
时,该Event
会通知所有等待状态的线程恢复运行
队列(中转站)、event事件(触发事件)、condition(条件)、Semaphore信号量(等候室)
import threading
import time
class MyThread(threading.Thread):
def __init__(self, signal):
threading.Thread.__init__(self)
self.singal = signal
def run(self):
print "I am %s,I will sleep ..."%self.name
self.singal.wait()#停下来even事件被设置
print "I am %s, I awake..." %self.name
if __name__ == "__main__":
singal = threading.Event()#创建event实例
for t in range(0, 3):
thread = MyThread(singal)
thread.start()
print "main thread sleep 3 seconds... "
time.sleep(3)
singal.set()#设置等待状态的线程变为执行状态
#####14、多进程和多线程的耗时比较
from multiprocessing.dummy import Pool as ThreadPool
from multiprocessing import Pool
import time
from functools import wraps
def func(num,r=0):
for i in range(1,num+1):
r += i
return r
def count_time(func):
@wraps(func)
def wrapper(*args,**kwargs):
s = time.perf_counter()
start = time.process_time()
r = func(*args,**kwargs)
end = time.process_time()
e = time.perf_counter()
print('%s总耗时:%s,cpu耗时%s'%(func.__name__,e-s,end-start))
return wrapper
@count_time
def process_func(sum):
for i in range(4):
process.apply_async(func=func,args=(sum,))
process.close()
process.join()
@count_time
def thread_func(sum):
for i in range(4):
thread.apply_async(func=func,args=(sum,))
thread.close()
thread.join()
if __name__ == '__main__':
thread = ThreadPool(4)
process = Pool(4)
process_func(10000000)
thread_func(10000000)