守护进程/线程、互斥锁、信号量
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕
#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
(所有非守护线程:不包括通过守护线程再开的子线程)
守护进程
import os, time, random from multiprocessing import Process def task(): print('%s is running'%os.getpid()) time.sleep(random.randint(1,3)) print('%s is done'%os.getpid()) if __name__ == '__main__': p = Process(target=task) p.daemon = True # 1. 守护进程 必须放在start开启之前 #2.守护进程不能再开启子进程(孤儿进程) p.start() print('主') #打印完'主',主进程代码执行完毕,如果还有其他子进程,主进程等待,但是守护进程在主进程代码执行完毕的同时也结束 # # 主
什么时候用守护进程呢?(守护进程的应用场景)
假设有2个任务要处理,要求并发效果,一个主进程,再开一个子进程;
1.如果2个任务毫无关系,正常开子进程就行
2.如果主进程的任务执行完毕,子进程的任务没有存在的意义了,那么就用守护进程(开启之前设置成守护)
守护进程什么时候死呢?
主进程代码执行完毕:
守护进程守护的是主进程代码执行的过程,过程,过程(重要的事情说3遍),主进程执行完毕了,守护进程就死掉了,和主进程死没死无关.
守护线程
守护线程可以再开子线程
import time, os, random from threading import Thread, current_thread def task(): print('%s is running task' % current_thread().getName()) time.sleep(random.randint(1, 3)) print('%s is done task' % current_thread().getName()) def func(): t1 = Thread(target=task) #守护线程可以再开子线程(守护线程死掉,守护线程的子线程也死掉) t1.start() print('%s is running func' % current_thread().getName()) time.sleep(random.randint(1, 3)) print('%s in done func' % current_thread().getName()) if __name__ == '__main__': t = Thread(target=func) t.daemon = True t.start() print('主') # 主 # Thread-2 is running task
守护线程什么时候死掉?
import time from threading import Thread, current_thread def task(): print('%s is running task' % current_thread().getName()) time.sleep(3) print('%s is done task' % current_thread().getName()) def func(): print('%s is running func' % current_thread().getName()) time.sleep(2) print('%s in done func' % current_thread().getName()) time.sleep(2) print('%s in done func' % current_thread().getName()) if __name__ == '__main__': t = Thread(target=func) t1 = Thread(target=task) t.daemon = True t.start() t1.start() print('主') # Thread-1 is running func # Thread-2 is running task # 主 #'主',主线程执行完毕,但是守护线程(func)没有死,直到所有的非守护线程执行完毕,才会强制杀死守护线程 #强制杀死守护进程后,整个进程就结束了,即使这个守护线程还有子线程,也无所谓,因为线程不存在孤儿概念 # Thread-1 in done func # Thread-2 is done task
守护线程可以再开子线程, 因为不存在孤儿进程,线程本来就在进程里面,进程都没了,还哪来的线程.
所有的非守护线程都执行完毕,守护线程才死掉
注意:
守护进程/线程 如果先一步执行完,会先结束
互斥锁
Lock( ):
进程锁和线程锁
from multiprocessing import Lock #进程锁 from threading import Lock #线程锁
如何加锁:
import os import time from multiprocessing import Process, Lock def func(mutex): print('%s print 1' % os.getpid()) time.sleep(2) mutex.acquire() #设置锁 print('%s print 2' % os.getpid()) time.sleep(2) print('%s print 3' % os.getpid()) mutex.release() #释放锁 if __name__ == '__main__': mutex = Lock() #创建一个锁 p1 = Process(target=func, args=(mutex,)) #为了保证所有进程都是同一把锁,把锁当成参数传进去 p2 = Process(target=func, args=(mutex,)) p3 = Process(target=func, args=(mutex,)) p1.start() p2.start() p3.start() #### 9896 print 1 9796 print 1 8128 print 1 print 1是并发效果 9896 print 2 9896 print 3 9796 print 2 9796 print 3 8128 print 2 8128 print 3 print 2,3 是串行效果
---
补充:
锁的简写方式,类似文件句柄的操作
with mutex: pass
注意事项:子进程会copy主进程的数据,如果不把锁传进子进程,会产生多把锁(每个进程都会有一把不同的锁)
p1 = Process(target=func) #子进程会copy主进程的数据,如果不把锁传进子进程,会产生多把锁(每个进程都会有一把不同的锁) p2 = Process(target=func) p3 = Process(target=func) p1.start() p2.start() p3.start()
示例: 如果把一个数字100,每次减去1,共减100次
1、如果不加锁:
import time from threading import Thread n = 100 def func(): global n mid = n time.sleep(1) n = mid - 1 if __name__ == '__main__': l = [] for i in range(100): t = Thread(target=func) #循环很快,不管哪个线程先拿到n,都要睡1秒才能修改数据,1秒的时间足够所有的线程都拿到n=100,所以最后的结果是 99, 如果把sleep()时间去掉,可能得到99/98..等等其他结果,但反应了同一个问题:数据不安去 l.append(t) t.start() for t in l: t.join() print(n) # 99
2、加上锁之后:
import os, time from threading import Thread, Lock n = 100 def func(): global n mutex.acquire() mod = n n = mod - 1 mutex.release() if __name__ == '__main__': mutex = Lock() l = [] for i in range(100): t = Thread(target=func) #线程因为共享数据,所以不需要把锁传进去 t.start() for t in l: t.join() print(n) # 0
通过示例,可以发现:没有锁,会造成数据不安全
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当
前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
总结:
互斥锁和join:
共同点:
都是把并发变成串行
不同点:
1、互斥锁:所有的进程/线程先执行起来(并发)
join():只能一个进程/线程执行完毕,才能开启下一个进程/线程
2、 互斥锁能锁住一部分(局部变成串行)
join只能锁住整个进程/线程
那么什么时候用互斥锁呢?
修改数据的时候(数据安全问题)
信号量Semaphore
import time, os, random from multiprocessing import Process, Semaphore def task(args): with args: print('%s running' % os.getpid()) time.sleep(random.randint(1, 3)) print('%s done' % os.getpid() if __name__ == '__main__': sm = Semaphore(3) #Semaphore(num) 设置信号量 for i in range(10): t = Process(target=task, args=(sm,)) t.start() #### 10192 running 8572 running 1644 running 三把锁,3个并发 10192 done 释放掉一个锁 9952 running 其他进程才能抢到锁 9952 done 8640 running 8572 done 8692 running 1644 done 6736 running 8692 done 10780 running 8640 done 10900 running 10780 done 10332 running 6736 done 10900 done 10332 done
信号量设置成(3)(有10个进程): 10个进程抢3个锁
进程池设置成(3): 从始至终只有3个进程
信号量和互斥锁:
互斥锁从始至终只有一个抢到,信号量可以设置多个,多个能抢到