一、进程
进程: 资源调度的最小单位,进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程 ,进程是系统进行资源分配的独立实体,且每个进程都有独立的内存地址,进程是线程的容器
(进程是同时进行的任务,比如QQ和微信,这两个应用程序就是进程)
每个进程有独立的进程号,也就是PID号
可以导入multiprocessing模块来创建进程
1、process
可以通过multiprocessing模块中的process来创建进程,进程要用start开启,
start和run: 直接调用 run() 方法时,它会在当前进程中运行,并不会创建新的进程。这种情况下,run() 方法只是一个普通的方法调用,而不是进程的启动。因此,如果想要多进程并行执行,必须使用 start() 方法来启动新的进程。
join: 进程等待,设置之后,主进程等待子进程结束之后再结束
daemon: 守护进程,开启守护进程的时候,主进程结束的时候,子进程也会被干掉,未完成的子进程不会有结果输出
from multiprocessing import Process # 导入模块
import time
def run(name, t):
print(f"{name} is running")
time.sleep(t) # 睡眠t秒
print(f"{name} is done ,use {t}s")
# 创建四个子进程
p1 = Process(target= run, args=("p1", 10))
# 创建进程,target是目标进程函数,args是相关参数,执行的时候会把args中擦参数放到target中执行
# args一定是元组形式
p2 = Process(target= run, args=("p2", 8))
p3 = Process(target= run, args=("p3", 6))
p4 = Process(target= run, args=("p4", 4))
ps = [p1, p2, p3, p4]
p1.daemon = True # 守护进程,主进程结束后,子进程也被干掉,不会打印
for p in ps:
p.start() # 进程要用start运行
# for p in ps:
# p.join() # 在子程序执行完成之后再结束主进程,如果把他直接加在p.start()后面就是一个子进程完成之后再进行另一个进程
time.sleep(2)
print("main process is done") # 主进程
2、Lock
Lock: 多进程情况下,如果不加锁,各进程之间管理是混乱的,相当于p1打印10张纸,p2打印8张纸,p3打印6张纸,如果不加锁,这互相之间是混乱的,可能先打印P1的2张纸,然后打印p2的3张纸,然后再打印p1的1张纸,如果对p1加了锁,那么就可以保证p1进程的连续性,就是连续打印p1的10张纸,然后打印p2
让程序更为有序
即使上锁之后,主进程也会及时结束,不会等子程序结束之后再结束
lock.acquire(): 上锁
lock.release(): 解锁
os.getpid(): 获取进程号,每个进程唯一
from multiprocessing import Process, Lock #导入lock
import os, time
def prints(lock:Lock): # 声明lock是Lock类型
lock.acquire() # 上锁
print(f"{os.getpid()} lock acquire") # os.getpid():进程的pid号
print(f"{os.getpid()} is running")
time.sleep(2) # 睡2s
print(f"{os.getpid()} is done")
print(f"{os.getpid()} lock release")
lock.release() # 解锁
lock = Lock()
for p in range(5):
ps = Process(target=prints, args=(lock,))
ps.start() # 结果就是一个进程运行完成之后再开始第二个进程,跟单进程一样
print("main process is done")
二、进程通信
进程通信: 各进程之间有不同的用户地址空间,一个进程的全局变量在另一个进程中看不到,进程之间的通信要通过内核,在内核中开辟一块儿缓冲区,把进程1的数据从用户空间拷贝到内核缓冲区,进程2再从缓冲区把数据读走
1、管道pipe
像管道一样,一端输送信息,一端获取信息,单向传输
也可以是双向传输
multiprocessing.Pipe(duplex=False)默认单向管道,改为True即为双向管道
管道实例化之后,就相当于[connection.Connection,connection.Connection],一个列表里两个元素,第一个是发:send,第二个是收:recv
from multiprocessing import Process, Pipe # 导入管道
import time
def producer(t, pip):
time.sleep(t)
pip.send("new goods")
print(f"make goods need {t}s")
def consumer(t, pip):
print(f"use {pip.recv()} need {t}s")
time.sleep(t)
pip.send("empty")
pip = Pipe() # 实例化之后pipe可以认为是一个列表[connection.Connection,connection.Connection],0是发,1是收
print(list(pip))
p = Process(target=producer, args=(10, pip[0])) # pip[0]是管道发的一端
c = Process(target=consumer, args=(5, pip[1])) # pip[1]是管道收的一端
ps = [p, c]
for p in ps:
p.start()
for p in ps:
p.join()
print("main process is done")
2、队列Queue
队列(Queue):创建共享的进程队列,是多进程安全的队列,可以使用Queue实现多进程之间的数据传递
Queue([maxsize])中maxsize是队列中允许的最大项数,省略则无限制
跟管道一样实现进程之间的通信
q.put(): 在队列中放一个数据
q.get(): 在队列中取一个数据
from multiprocessing import Process, Queue #导入队列
def write_process(q):
q.put(5) # 放入一个数据5
def read_process(q):
print(q.get()) # 拿出来一个数据
q = Queue() # 不上传参数则无队列限制
p_w = Process(target=write_process, args=(q,))
p_r = Process(target=read_process, args=(q,))
p_w.start()
p_r.start()
print("main process is done")
生产者消费者模型
单生产者,单消费者
from multiprocessing import Process, Queue
import time, os
def consumer(q:Queue): # 消费者, 消费数据,从管道/队列中取数据
while True:
res = q.get()
time.sleep(2)
print(f"p{os.getpid()} get {res}")
def producer(q:Queue): # 生产者,生产数据,往管道/队列中存放数据
for i in range(10):
time.sleep(3)
res = "box%s" % i
q.put(res)
print(f"p{os.getpid()} put {res}")
q = Queue() # 队列实例化
p = Process(target=producer, args=(q,))
c = Process(target=consumer, args=(q,))
p.start()
c.start()
print("main process is done!")
3、JoinableQueue
队列
q.task_done(): 相比Queue多了一个task_done(),主要作用是,在消费者处理完任务之后,会发出信号,告诉生产者可以继续生产,然后生产者收到信号之后就可以继续生产
用于多生产者多消费者模型
【具体结果的显示顺序和time.sleep()的时间有关系】
from multiprocessing import Process, JoinableQueue, Queue
import os, time
# 【具体结果的显示顺序和time.sleep()的时间有关系】
def consumer(name, q, t):
while True:
res = q.get()
time.sleep(t)
print(f"consumer{name}: {os.getpid()} --- {res}")
q.task_done() # 如果去掉task_done()则会在进行完一轮之后(生产3消费3之后)卡住,因为消费者【没有】发出信号表示任务已处理,生产者不会接受到继续生产的信号
# 消费者发出信号,表示任务已处理,然后生产者可以继续生产
def producer(name, q, t):
for i in range(4):
time.sleep(t)
res = f"{os.getpid()}_{i}:{name}"
q.put(res)
print(f"producer : {os.getpid()} --- {res}")
# q.join() # 如果q.join()放在与for同级别,则会乱序
# join的作用是等待消费者消费,如果去掉join则会出现,生产者1生产的产品1还没有被消费者消费掉,生产者1继续生产产品2
# 正常情况是,生产者1生产的产品1被消费者消费掉之后,再继续生产产品2
q.join() # join()如果放在这里则表示,生产者生产一批之后,再等待消费者消费,如果放在for循环里面,表示生产者生产一个就等待消费者消费
# join()如果放在外面表示消费者是有序的
q = JoinableQueue() # 如果使用JoinableQueue,不使用task_done和join则会和Queue一样,单纯是两个进程之间的通信
# q = Queue() # 如果使用Queue,则是单纯的两个进程之间的通信
p1 = Process(target=producer, args=("生产者1", q, 1))
p2 = Process(target=producer, args=("生产者2", q, 1))
p3 = Process(target=producer, args=("生产者3", q, 1))
c1 = Process(target=consumer, args=("消费者1", q, 1))
c2 = Process(target=consumer, args=("消费者2", q, 1))
c3 = Process(target=consumer, args=("消费者3", q, 1))
c1.daemon = c2.daemon = True
ps = [p1, p2, p3]
cs = [c1, c2, c3]
for p in ps:
p.start()
for c in cs:
c.start()
for p in ps:
p.join()
for c in cs:
c.join()
print("main process is done!")
4、信号量Semaphore
semaphore:信号量(信号标) ,用于保持0至最大值之间的一个计数值,当线程完成一次对该semaphore对象的等待时,该计数减一,当线程完成一次对semaphore对象的释放时,计数加一;
当计数为0时,则进程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态
semaphore对象的计数值大于0 ,则为signaled状态,计数等于0,则为nonsignaledz状态
semaphore = -4 ,表示有4个进程在等待着
s.acquire() : 消耗一个资源
s.release() : 释放一个资源
from multiprocessing import Process, Semaphore #导入信号量
import os, time
def prints(s):
s.acquire() # 消耗一个资源
print(f"------p{os.getpid()} is acquire-----{s}")
print(f"p{os.getpid()} is running{s}")
time.sleep(5)
print(f"p{os.getpid()} is done")
print(f"------p{os.getpid()} is release-----")
s.release() # 释放一个资源
s = Semaphore(2) # Semaphore表示最多允许几个进程同时进行,等于1则相当于单进程
for i in range(3):
p = Process(target=prints, args=(s,))
p.start()
5、进程池Pool
进程池:pool ([numprocess[, initializer[, initargs]]])
numprocess:要创建的进程数,如果省略,将使用默认cpu_count的值
initializer:每个工作进程启动时要执行的可调用对象,默认None
initargs:要传给initializer的参数组
如果使用pool创建numprocess为3,则会自始至终使用这三个进程,进程号不会变,不会开启其他进程
【Semaphore会产生其他进程,会有新的pid出现,但是pool不会】
p.apply (func[,arges[, kwargs]]):同步调用,在一个池进程中执行func然后返回结果
from multiprocessing import Pool, Process # 导入进程池
import os, time
def work(n):
print(f"p{os.getpid()} is running")
time.sleep(3)
return n ** 2
p = Pool(3) # pid始终都是3个,不会有其他
result = []
for i in range(10):
res = p.apply(work, args=(i, ))
result.append(res)
print(result)
p.apply_async(func[,arges[, kwargs]]):异步调用
同步和异步的区别:
【同步:一个进程一个进程显示; 异步:三个三个的显示】
【异步的效率更快】
异步调用得到的数据不是数字,是multiprocessing.pool.applyresult 对象,需要get()拿到
p.close(): 关闭进程池,防止进一步操作
p.join(): 等待所有工作进程退出,此方法只能在close()之后调用
from multiprocessing import Pool
import time, os
def work(n):
print(f"p{os.getpid()} is running")
time.sleep(3)
return n ** 2
p = Pool(3)
result = []
for i in range(10):
res = p.apply_async(work, args=(i, ))
result.append(res)
p.close()
p.join()
# print(result) # 此时result里面的数据不是数字,而是multiprocessing.pool.applyresult 对象,需要get()拿到
for res in result:
print(res.get(), end=",")
print()
三、线程及线程通信
1、GIL锁:
GIL 保证了Python解释器的内存安全,也限制了Python的多线程性能。因为同一时刻只有一个线程可以执行Python字节码,其他线程只能等待。这意味着,即使在多核CPU上,Python解释器也不能同时利用多个CPU核心来执行Python代码。
python2是元编程机制,python3是时间片轮转机制
2、线程:
进程是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。
使用threading.Thread来创建线程
import threading # 导入threading包
import time
def run(n, t):
print(f"{n} is running")
time.sleep(t)
print(f"{n} is done used {t}s")
if __name__ == '__main__':
t1 = threading.Thread(target=run, args=("t1", 5)) # 创建线程
t2 = threading.Thread(target=run, args=("t2", 7))
t1.start()
t2.start()
自定义线程
class MyThread(threading.Thread):
def __init__(self, n, t):
super(MyThread, self).__init__()
self.n = n
self.t = t
def run(self): # 重构run函数,必须!
print(f"{self.n} is running")
time.sleep(self.t)
print(f"{self.n} is done uesd {self.t}s")
if __name__ == '__main__':
t1 = MyThread('t1', 5)
t2 = MyThread('t2', 7)
t1.start()
t2.start()
3、守护线程
作用和进程一样,在主线程结束后,未结束的线程会被干掉,不会有输出结果
写法一: t.setDaemon(True)
写法二:t.daemon = True
def run(n, t):
print(f"{n} is running")
time.sleep(t)
print(f"{n} is done used {t}s")
if __name__ == '__main__':
t = threading.Thread(target=run, args=("t1", 5))
t.setDaemon(True) # 线程守护
# t.daemon = True # 线程守护的另一种写法
t.start()
t.join() # 防止主线程结束杀死子线程
print("end")
4、多线程共享全局变量
g_num = 100 # 全局变量
def work1():
global g_num
for i in range(3):
g_num += 1
print('in work1 g_num is : %d' %g_num)
def work2():
global g_num
print('in work2 g_num is : %d' %g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=work1) # work1 使全局变量+1
t1.start()
time.sleep(3)
t2 = threading.Thread(target=work2) # work2 访问全局变量
t2.start()
# 结果是
# in work1 g_num is : 103
# in work2 g_num is : 103
# 说明各线程之间的数据共享
5、线程锁
**lock,**同进程一样
未加锁
# 未加锁,结果不是0,多线程的时候,add和sub同时进行,会导致结果覆盖
# 相当于add拿到数据,进行运算,sub也会拿到数据进行运算,add结果返回原数据之后,sub再返回原数据就会将add的结果覆盖掉
n = 0
lock = Lock() # 定义了lock,但是没有用
def add():
global n, lock
for _ in range(500000):
n += 1
def sub():
global n
for _ in range(500000):
n -= 1
if __name__ == '__main__':
thread_add = Thread(target=add)
thread_sub = Thread(target=sub)
thread_add.start()
thread_sub.start()
thread_add.join()
thread_sub.join()
print('n=', n, flush=True)
加锁
# 加锁 结果是0
n = 0
lock = Lock()
def add():
global n, lock
for _ in range(500000):
lock.acquire()
n += 1
lock.release()
def sub():
global n
for _ in range(500000):
lock.acquire()
n -= 1
lock.release()
if __name__ == '__main__':
thread_add = Thread(target=add)
thread_sub = Thread(target=sub)
thread_add.start()
thread_sub.start()
thread_add.join()
thread_sub.join()
print('n=', n, flush=True)
6、信号量
用法和进程的用法一样
但是由于线程组成了进程,所以多线程运行的时候,其实是在同一个进程中运行,
因此多线程获取到的Pid都是一样的
import threading
import time
produced = threading.Semaphore(3) #用法和进程一样
def producer(i):
produced.acquire()
print(f"{i} is running")
time.sleep(10)
produced.release()
for i in range(20):
t1 = threading.Thread(target=producer, args=(os.getpid(), ))
t1.start()
# 结果是三个三个一起蹦,但是所有的pid都是一样的
7、线程池
用法和进程池一样
from concurrent.futures import ThreadPoolExcutor # 导入