python基础(进程的锁机制,进程队列,进程数据共享,进程池与回调函数)
一、 进程锁(Lock):
1.1 定义:
1. 同一时刻同一段代码,只能有一个进程来执行这段代码
2. 锁的应用场景,当多个进程需要操作同一个文件/数据库的时候 ,
3. 会产生数据不安全,我们应该使用锁来避免多个进程同时修改一个文件
1.2 示例:
# 示例: import json import time from multiprocessing import Process, Lock # 查询余票 def search_ticket(name): with open('ticket', encoding='utf-8') as f: dic = json.load(f) print('%s查询余票为%s' % (name, dic['count'])) # 购买车票 def buy_ticket(name): with open('ticket', encoding='utf-8') as f: dic = json.load(f) time.sleep(2) if dic['count'] >= 1: print('%s买到票了' % name) dic['count'] -= 1 time.sleep(2) with open('ticket', mode='w', encoding='utf-8') as f: json.dump(dic, f) else: print('余票为0,%s没买到票' % name) # 使用线程锁方式一: def use1(name, lock): search_ticket(name) print('%s在等待' % name) lock.acquire() print('%s开始执行了' % name) buy_ticket(name) lock.release() # 使用线程锁方式二: def use2(name, lock): """ # with lock: # 代码块 # 上下文管理:在__enter__方法中获取锁(acquire),在__exit__方法中释放锁(release) """ search_ticket(name) print('%s在等待' % name) with lock: print('%s开始执行了' % name) buy_ticket(name) if __name__ == '__main__': lock = Lock() l = ['alex', 'wusir', 'baoyuan', 'taibai'] for name in l: Process(target=use1, args=(name, lock)).start() # 方式一 Process(target=use2, args=(name, lock)).start() # 方式二
二、进程程队列(Queue):
2.1 定义:
多个进程之间的数据是隔离的
进程之间的数据交互可以通过网络/文件来实现的(socket)
2.2 进程通信的实现方式:
通过python的模块实现的:
基于原生socket
基于进程队列的
第三方的软件/工具来实现 : 基于网络的:
memcache redis rabbitMQ kafka - 软件名
IPC(进程之间的通信) - inter process communication
进程:Queue from multiprocessing import Queue 可以完成进程之间通信的特殊的队列 队列:Queue from queue import Queue 不能完成进程之间的通信,普通的队列
2.3 进程队列示例:
# 队列(queue)示例: from multiprocessing import Queue, Process def son(q): print('-->', q.get()) # 获取队列里面的值 if __name__ == '__main__': q = Queue() Process(target=son, args=(q,)).start() # 创建一个子进程 q.put('wahaha') # 入队
三、 生产者与消费者模型:
3.1 定义:
1. 调节生产者的个数或者消费者的个数来让程序的效率达到最平衡和最大化
2. 解耦思想
示例:
# 生产者与消费者示例: import time import random from multiprocessing import Process, Queue # 生产者 def producer(q): for i in range(10): time.sleep(random.random()) food = 'Spam %s' % i print('%s生产了%s' % ('Jane', food)) q.put(food) # 消费者 def consumer(q, name): while True: food = q.get() # food = 食物/None if not food: break # 当消费者消费完成后,最后拿到None时,退出消费者程序 time.sleep(random.uniform(1, 2)) print('%s 吃了 %s' % (name, food)) if __name__ == '__main__': q = Queue() p1 = Process(target=producer, args=(q,)) # 生产者 p1.start() c1 = Process(target=consumer, args=(q, 'Lisa')) # 消费者 c1.start() c2 = Process(target=consumer, args=(q, 'Annie')) # 消费者 c2.start() p1.join() q.put(None) # 生产者完成生产后,队列最后加入None,表示已经生产完成 q.put(None) # 每个消费者需要None退出程序
3.2 共享进程队列(消费者与生产者):
定义:
1.生产者生产完数据之后,一直阻塞等待消费者完成消费消费者队列为空时(task_done())返回一个消息给阻塞等待的生产者(join()), 生产者接收消息自动结束当前进程
2.把消费者设置为主进程的守护进程,当主进程的代码块结束后,守护进程(消费者)进程结束
示例:
# 共享进程队列示例: import time import random from multiprocessing import JoinableQueue, Process # 消费者 def consumer(jq, name): while True: food = jq.get() time.sleep(random.uniform(1, 2)) # 模拟消费时间 print('%s吃完%s' % (name, food)) jq.task_done() # 消费完成时(队列为空),返回一个消费完成的信息给到生产者 # 生产者 def producer(jq): for i in range(10): time.sleep(random.random()) food = 'Spam%s' % i print('%s生产了%s' % ('Jane', food)) jq.put(food) jq.join() # 接收到消费者队列为空,阻塞的生产者程序结束 if __name__ == '__main__': jq = JoinableQueue(5) c1 = Process(target=consumer, args=(jq, 'Annie')) # 消费者 p1 = Process(target=producer, args=(jq,)) # 生产者 c1.daemon = True # 设置消费者为守护进程 c1.start() p1.start() p1.join() # 等待生产者进程结束,标志主进程代码块结束
四、 进程之间的数据的数据共享(Manager类):
多次开启进程,管理进程,销毁进程会拖慢进程的执行速度
Manager类可以实现数据共享,当Manager类中对列表/字典操作(+= -= /= *=)的情况数据不安全,需要进行加锁保证数据安全
示例:
# 数据共享: from multiprocessing import Process, Manager, Lock def func(dic, lock): lock.acquire() # 加锁 dic['count'] -= 1 # 子进程的数据修改 lock.release() # 释放锁 if __name__ == '__main__': lock = Lock() m = Manager() # 实例化一个Manager类 dic = m.dict({'count': 100}) # 通过对象创建一个字典 p_l = [] for i in range(100): p = Process(target=func, args=(dic, lock)) p.start() p_l.append(p) for p in p_l: p.join() # 阻塞等待子程序结束 print(dic)
五、 进程池(concurrent.futures类)
5.1 为什么需要创建进程池:
1.不能有多少个任务就开多少个进程,这样开销太大了
2.用有限的进程执行无限的任务,多个被开启的进程重复利用,节省的是开启\销毁\多个进程切换的时间
5.2 进程池的创建:
# 方式一(for循环): import os from concurrent.futures import ProcessPoolExecutor def make(i): print('%s 制作螺丝%s' % (os.getpid(), i)) return i ** 2 if __name__ == '__main__': p = ProcessPoolExecutor(4) # 创建一个进程池 for i in range(100): p.submit(make, i) # 向进程池中提交任务 p.shutdown() # 阻塞 直到池中的任务都完成为止 print('所有的螺丝都制作完了')
# 方式二(map实现): import os from concurrent.futures import ProcessPoolExecutor def make(i): print('%s 制作螺丝%s' % (os.getpid(), i)) return i ** 2 if __name__ == '__main__': p = ProcessPoolExecutor(4) # 创建一个进程池 p.map(make, range(100)) # submit的简便用法 p.shutdown() # 阻塞 print('所有的螺丝都制作完了')
5.3 进程池的对象的返回值:
# 进程池的返回值: import os import time from concurrent.futures import ProcessPoolExecutor def make(i): time.sleep(0.2) print('%s 制作螺丝%s' % (os.getpid(), i)) return i ** 2 if __name__ == '__main__': ret_l = [] p = ProcessPoolExecutor(4) # 创建一个进程池 # 方式一: for i in range(100): ret = p.submit(make, i) ret_l.append(ret) for r in ret_l: print(r.result()) # 返回每个子线程的返回值 # 方式二 ret = p.map(make, range(100)) # 返回一个生成器对象 print(ret) # <generator object _chain_from_iterable_of_lists at 0x00000215F9D83EB8> for i in ret: # 每次获取到一个返回值 print(i)
六、 进程池与回调函数:
# 回调函数: # 示例: import time import random from concurrent.futures import ProcessPoolExecutor # 子进程 def func1(n): time.sleep(random.random()) # 生成一个0-1之间的随机小数 print('in func1 %s' % n) return n * 2 # 回调函数 def call_back(arg): print(arg) # <Future at 0x258cca91c18 state=finished returned int> 结果对象 print(arg.result()) # 结果值,例如:4,6,10... if __name__ == '__main__': p = ProcessPoolExecutor(4) # 创建一个进程池 # 方式一: for i in range(10): # 提交10个任务 ret = p.submit(func1, i) # # print(ret) # <Future at 0x2050be96438 state=running> 返回一个结果对象 ret.add_done_callback(call_back) # 函数回调call_back # 方式二: ret_l = [] for i in range(10): ret = p.submit(func1, i) # 返回结果对象 ret_l.append(ret) for r in ret_l: call_back(r) # 循环回调