进程补充

进程通信

要想实现进程间通信,可以用管道或者队列

队列比管道更好用(队列自带管道和锁)

管道和队列的共同特点:数据只有一份,取完就没了 无法重复获取用一份数据

队列特点:先进先出

堆栈特点:先进后出

我们采用队列来实现进程间数据通信,下面先介绍一下队列

Queue队列的基本使用

基本方法:q.put(元素) q.get() q.get_nowait() q.full() q.empty()

基础版

  1. from multiprocessing import Queue
  2. q = Queue() # 括号内支持传数字 限制的是队列的大小
  3. # 队列先进先出
  4. # 堆栈先进后出
  5. q.put()放队列中放数据
  6. q.get()从队列中取数据
  7. # 上面两个方法都会阻塞
  8. q.full()判断队列是否存满
  9. q.empty()判断队列是否取空
  10. q.get_nowait()取值一旦没有值不会原地等待直接报错
  11. # 上面三个方法在多进程/多线程不适用

进阶版

  1. from multiprocessing import Process, Queue
  2. q = Queue(5) # 实例化出一个对象
  3. # --------------------------------------
  4. # q.put(元素) 往队列里放东西
  5. # 如果队列满了还往里面放,就会等在这里
  6. # --------------------------------------
  7. # q.put(1)
  8. # q.put(2)
  9. # q.put(3)
  10. # --------------------------------------
  11. # # q.full() 判断队列有没有满
  12. # --------------------------------------
  13. # print(q.full()) # q.full 判断队列有没有满
  14. # # False
  15. # q.put(4)
  16. # q.put(5)
  17. # # q.put(6) # 如果队列满了还往里面放,就会等在这里
  18. # print(q.full())
  19. # # True
  20. for i in range(5):
  21. q.put(i)
  22. print(q.full())
  23. # True
  24. # --------------------------------------
  25. # q.get() 从队列头取一个值
  26. # 如果队列空了,就会等在这里,等数据过来
  27. # --------------------------------------
  28. print(q.get())
  29. print(q.full())
  30. # 0
  31. # False
  32. print(q.get())
  33. print(q.get())
  34. # print(q.get())
  35. # --------------------------------------
  36. # q.get_nowait() 从队列头取一个值
  37. # 在队列有数据的情况下,与get取值一样
  38. # 当队列没有值的情况下,取值直接报错
  39. # --------------------------------------
  40. print(q.get_nowait()) # 在队列有数据的情况下,与get取值一样,当队列没有值的情况下,取值直接报错
  41. # --------------------------------------
  42. # q.empty() 判断队列是否为空
  43. # 在并发的情况下,这个方法不准确
  44. # --------------------------------------
  45. print(q.empty()) # 判断队列是否为空,需要注意的是在并发的情况下,这个方法不准确
  46. print(q.get())
  47. # 1
  48. # 2
  49. # 3
  50. # False
  51. # 4
  52. # print(q.get()) # 如果队列空了,就会等在这里,等数据过来
  53. print(q.empty())
  54. # True
  55. # print(q.get_nowait())
  56. # 直接报错 queue.Empty

通过Queue队列实现进程间通信( IPC机制 )

数据的互通可实现主进程与子进程之间的互通,子进程与子进程之间的互通

  1. from multiprocessing import Queue, Process
  2. def producer(q):
  3. q.put('hello baby.')
  4. def consumer(q):
  5. print(q.get())
  6. if __name__ == '__main__':
  7. q = Queue() # 生成一个队列对象
  8. p1 = Process(target=producer, args=(q,))
  9. c1 = Process(target=consumer, args=(q,))
  10. p1.start()
  11. c1.start() # 子进程获取到了另一个子进程的数据
  12. # hello baby.
  13. # print(q.get()) # 主进程获取到了子进程的数据
  14. # hello baby.

生产者消费者模型

生产者:生产/制造数据的

消费者:消费/处理数据的

两者之间的沟通介质:队列

  1. 例子:做包子的,卖包子的
  2. 1.做的包子远比买包子的多
  3. 2.做的包子远比买包子的少
  4. --> 供需不平衡

用处:(利用队列)解决供需不平衡的问题

需求:以卖包子为例,实现当包子卖完了就停止消费行为

也就是做到消费者消费完数据之后代码立即结束

  1. 1.利用join等待生产者生产完数据 再往队列中添加特定信息(None)
  2. 有几个消费者就必须有几个None(对应下面的方式一)
  3. 2.JoinableQueue 能够被joinq
  4. q.task_done() 告诉队列数据被取出
  5. q.join() 等待队列数据完全被取完
  6. 将所有的消费者设置为守护进程(对应下面的方式二)

方式一

  1. from multiprocessing import Process, Queue
  2. import time
  3. import random
  4. def producer(name, food, q: Queue):
  5. for i in range(10):
  6. data = f'{name} 生产了 {food}{i}'
  7. time.sleep(random.random())
  8. q.put(data)
  9. print(data)
  10. def consumer(name, q):
  11. while True:
  12. res = q.get()
  13. if not res: # 已经把生产者做的东西全部吃完了,那么本消费者也结束食用
  14. break
  15. data = res.split(' ')[2]
  16. data = f'{name} 吃了 {data}'
  17. print(data)
  18. time.sleep(random.random())
  19. if __name__ == '__main__':
  20. q = Queue()
  21. p = Process(target=producer, args=('大厨egon', '馒头', q))
  22. p2 = Process(target=producer, args=('跟班tank', '生蚝', q))
  23. c = Process(target=consumer, args=('jason', q))
  24. c2 = Process(target=consumer, args=('吃货kevin', q))
  25. p.start()
  26. p2.start()
  27. c.start()
  28. c2.start()
  29. # 不知道什么时候生产者什么时候生成完
  30. p.join()
  31. p2.join()
  32. q.put(None) # 通过 None来标志生产者已生产完成
  33. q.put(None)
  34. # 可以实现,但是不好

方式二

改用JoinableQueue模块的队列守护进程来实现

  1. from multiprocessing import Process, JoinableQueue
  2. import time
  3. import random
  4. def producer(name, food, q: JoinableQueue):
  5. for i in range(10):
  6. data = f'{name} 生产了 {food}{i}'
  7. time.sleep(random.random())
  8. q.put(data)
  9. print(data)
  10. def consumer(name, q):
  11. while True:
  12. res = q.get()
  13. if not res:
  14. break
  15. data = res.split(' ')[2]
  16. data = f'{name} 吃了 {data}'
  17. print(data)
  18. time.sleep(random.random())
  19. q.task_done() # 告诉队列,你已经从队列中取出了一个数据,并且处理完毕了
  20. if __name__ == '__main__':
  21. q = JoinableQueue()
  22. p = Process(target=producer, args=('大厨egon', '馒头', q))
  23. p2 = Process(target=producer, args=('跟班tank', '生蚝', q))
  24. c = Process(target=consumer, args=('jason', q))
  25. c2 = Process(target=consumer, args=('吃货kevin', q))
  26. p.start()
  27. p2.start()
  28. c.daemon = True # 配合join,结束程序消费者也结束(注意join是主进程的最后一句代码)
  29. c.start()
  30. c2.daemon = True
  31. c2.start()
  32. # 不知道什么时候生产者什么时候生成完
  33. p.join()
  34. p2.join()
  35. q.join() # 等待队列中数据全部取出,执行完了这句话,也就意味着队列中没有数据了(消费者那里还是会卡住,get不到东西等待)
  36. # 配合上 守护进程 来实现....

线程

什么是线程?

进程和线程其实都是虚拟单位,都是用来帮助我们形象的描述某种事物

进程:资源单位(一块独立的内存空间)(车间)

线程:执行单位(流水线)

将内存比喻成工厂,那么进程就相当于工厂里的车间,而你的线程就相当于是车间里面的流水线

CPU其实运行的其实是线程,进程只是资源单位

线程执行时需要的资源单位都跟进程要

ps:每个进程都自带一个“主线程”,线程才是真正的执行单位,进线程在运行的时候所需要和产生的数据都是来源于当前线程所在的进程

一个进程下可以开设多个线程

线程没有主次之分,只不过我们默认就把主进程自带的那个线程叫做主线程

为什么要有线程?

开进程

  • 申请内存空间 ---> 耗资源
  • “拷贝代码” ---> 耗资源

开线程

  • 进程间数据是隔离但是同一个进程下的多个线程数据是共享的

ps:开启线程的开销要远远小于开启进程的开销(可能刚执行完创建线程的代码线程就创建好了)

开启线程的两种方式

方式一(1.利用类名里面传target,args参数来开设线程)

  1. from threading import Thread
  2. import time
  3. def task(name):
  4. print(f"{name} is running")
  5. time.sleep(3)
  6. print(f"{name} is over")
  7. t = Thread(target=task, args=('egon', )) # 开线程不需要在 __main__ 代码块内,但是习惯性的还是写在 __main__ 内
  8. t.start() # 告诉操作系统开启一个线程
  9. # 线程的开销远远小于进程,小到以至于可以代码执行完,线程就已经开启了
  10. print("主") # 线程没有主次之分,都在同一个进程的名称空间里,只是人为把进程自带的线程叫做主线程
  11. # egon is running
  12. # 主线程 # 进程的时候这个主线程可能会是最先打印的
  13. # egon is over

方式二(2.自定义类进程线程类 通过定义暴露的接口run方法)

ps:windows中开设进程必须在__main__代码块内 而开线程不需要

  1. from threading import Thread
  2. import time
  3. class MyThread(Thread):
  4. def __init__(self, name):
  5. super().__init__()
  6. self.name = name
  7. def run(self):
  8. print(f"{self.name} is running")
  9. time.sleep(1)
  10. print(f"{self.name} is over")
  11. if __name__ == '__main__':
  12. t = MyThread('jason')
  13. t.start() # 开启线程的速度非常快,几乎代码执行完线程就已经开启
  14. print("主")
  15. # jason is running
  16. # 主
  17. # jason is over

线程之间的数据共享

  1. from threading import Thread
  2. money = 666
  3. def task():
  4. global money
  5. money = 999
  6. t = Thread(target=task)
  7. t.start()
  8. t.join() # 确保是线程运行结束后
  9. print(money)
  10. # 999 # 主线程与子线程之间数据是通用的

线程间想要实现数据通信,不需要借助于队列(线程间支持数据通信)

线程对象的其他属性和方法

  1. import time
  2. from threading import Thread, active_count, current_thread
  3. import os
  4. def task(name):
  5. print(f"{name} is running {os.getpid()}")
  6. # # ------------------------------------------------
  7. # # current_thread().name current_thread().getname() 当前线程名
  8. # # 记得导入模块
  9. # # ------------------------------------------------
  10. # print(f"current_thread().name:{current_thread().name}")
  11. # current_thread().name:Thread-1
  12. time.sleep(1)
  13. print(f"{name} is over")
  14. # t = Thread(target=task, args=('jason', ))
  15. # t.start()
  16. # # ------------------------------------------------
  17. # # os.getpid() os.getppid() 获取进程号 父进程号
  18. # # 多个线程属于同一进程
  19. # # ------------------------------------------------
  20. # print(f"pid {os.getpid()}")
  21. # # jason is running 5572
  22. # # pid 5572
  23. # # jason is over
  24. t = Thread(target=task, args=('jason', ))
  25. t.start()
  26. # ------------------------------------------------
  27. # active_count() 统计当前存活的线程数
  28. # 记得导入模块
  29. # ------------------------------------------------
  30. print(active_count())
  31. print(f"pid {os.getpid()}")
  32. # jason is running 5728
  33. # 2
  34. # pid 5728
  35. print(f"主 current_thread().name:{current_thread().name}")
  36. # 主 current_thread().name:MainThread
  37. t.join() # 主线程等待子线程运行结束
  38. # jason is over
  39. print("主 active_count", active_count()) # 可能会有问题,多线程是异步,可能join的线程结束了,其他线程也正好结束了(多个线程时)
  40. # 主 active_count 1
  41. # Thread.join(t) # 可以考虑用类调用对象方法,传入对象来在循环里对线程对象进行操作

线程运用互斥锁

  1. # # 将这个100 每个线程减一
  2. # import random
  3. # from threading import Thread
  4. # import time
  5. #
  6. # n = 100
  7. #
  8. #
  9. # def task():
  10. # global n
  11. # tmp = n
  12. # time.sleep(random.randint(1, 3))
  13. # n = tmp - 1
  14. #
  15. #
  16. # threading_list = []
  17. # for i in range(100):
  18. # t = Thread(target=task)
  19. # t.start()
  20. # threading_list.append(t)
  21. #
  22. # for t in threading_list:
  23. # t.join()
  24. #
  25. # print(n)
  26. # 利用互斥锁来实现
  27. import random
  28. from threading import Thread, Lock # 和multi...模块里的是一样的,所以用法也一样
  29. import time
  30. n = 100
  31. def task(mutex):
  32. global n
  33. mutex.acquire()
  34. tmp = n
  35. time.sleep(random.random())
  36. n = tmp - 1
  37. mutex.release()
  38. threading_list = []
  39. mutex = Lock()
  40. for i in range(100):
  41. t = Thread(target=task, args=(mutex,))
  42. t.start()
  43. threading_list.append(t)
  44. for t in threading_list:
  45. t.join()
  46. print(n)
  47. # 0 # 等待一段时间后才会打印出结果

守护线程

主线程要等待所有非守护线程结束后才会结束(不是主线程的代码执行完了就立马结束了)

主线程结束后,守护(子)线程也会立即结束

主线程运行结束之后为什么需要等待子线程结束才能结束呢?

主线程的结束也就意味着进程的结束

主线程必须等待其他非守护线程的结束才能结束

因为子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了,资源也就销毁了

  1. # from threading import Thread, current_thread
  2. # import time
  3. #
  4. #
  5. # def task(i):
  6. # print(f"{current_thread().name}")
  7. # time.sleep(i)
  8. # print("GG")
  9. #
  10. #
  11. # for i in range(3):
  12. # t = Thread(target=task, args=(i, ))
  13. # t.start()
  14. #
  15. #
  16. # print("主")
  17. # # 循环的时候就已经打印了部分数据了(异步)
  18. # # Thread-1
  19. # # GG
  20. # # Thread-2
  21. # # Thread-3
  22. # # 主
  23. # # GG
  24. # # GG
  25. # 主线程运行结束之后为什么需要等待子线程结束才能结束呢?
  26. '''
  27. 主线程的结束也就意味着进程的结束
  28. 主线程必须等待其他非守护线程的结束才能结束
  29. 因为子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了,资源也就销毁了
  30. '''
  31. from threading import Thread, current_thread
  32. import time
  33. def task(i):
  34. print(f"{current_thread().name}")
  35. time.sleep(i)
  36. print("GG")
  37. for i in range(3):
  38. t = Thread(target=task, args=(i,))
  39. t.daemon = True
  40. t.start()
  41. print("主")
  42. # Thread-1
  43. # GG
  44. # Thread-2
  45. # Thread-3
  46. # 主

测试

下面程序的执行结果是什么?

  1. from threading import Thread
  2. import time
  3. def foo():
  4. print(123)
  5. time.sleep(1)
  6. print("end123")
  7. def bar():
  8. print(456)
  9. time.sleep(3)
  10. print("end456")
  11. t1 = Thread(target=foo)
  12. t2 = Thread(target=bar)
  13. t1.daemon = True
  14. t1.start()
  15. t2.start()
  16. print("main-------")
  17. # 123
  18. # 456
  19. # main-------
  20. # end123
  21. # end456

线程互斥锁

基础版

多个线程修改同一份数据会造成数据错乱的问题 所以需要加锁

加了锁,保证了数据的安全 但是降低了代码的运行效率 因为你将并发变成了串行

ps:针对不同的数据 应该加不同的锁进行处理

抢锁 acquire()

释放锁 release()

进阶版

从线程间通信那里的案例可以看出,线程间数据是相通的,那么多个线程对同一份数据进行操作会产生问题

下面同样模拟一个网络延迟来对数据进行操作(确保所有线程都执行完的操作可以记一下)

不加锁遇到延迟的情况

  1. # 模拟网络延迟的现象
  2. # 多个线程操作同一个数据,也会造成数据不安全
  3. import time
  4. from threading import Thread
  5. n = 10
  6. def task():
  7. global n
  8. tmp = n
  9. time.sleep(1)
  10. n = tmp - 1
  11. # -------------------------------
  12. t_list = []
  13. for i in range(10):
  14. t = Thread(target=task)
  15. t.start()
  16. t_list.append(t)
  17. # 确保其他线程都执行完了之后再打印
  18. for t in t_list:
  19. t.join()
  20. # -------------------------------
  21. print(n)
  22. # 9

加锁后遇到延迟

  1. # 加锁解决问题
  2. import time
  3. from threading import Thread, Lock
  4. n = 10
  5. def task(mutex):
  6. mutex.acquire() # 抢锁
  7. global n
  8. tmp = n
  9. time.sleep(1)
  10. n = tmp - 1
  11. mutex.release() # 释放锁
  12. t_list = []
  13. mutex = Lock()
  14. for i in range(10):
  15. t = Thread(target=task, args=(mutex, ))
  16. t.start()
  17. t_list.append(t)
  18. # 确保其他线程都执行完了之后再打印
  19. for t in t_list:
  20. t.join()
  21. print(n)
  22. # 0 # 等10s多点 后打印出结果,数据未受延迟影响,保证了数据安全

线程和进程的用户大同小异,可以对比着来记

8.12 day31 进程间通信 Queue队列使用 生产者消费者模型 线程理论 创建及对象属性方法 线程互斥锁 守护线程的更多相关文章

  1. python并发编程-进程间通信-Queue队列使用-生产者消费者模型-线程理论-创建及对象属性方法-线程互斥锁-守护线程-02

    目录 进程补充 进程通信前言 Queue队列的基本使用 通过Queue队列实现进程间通信(IPC机制) 生产者消费者模型 以做包子买包子为例实现当包子卖完了停止消费行为 线程 什么是线程 为什么要有线 ...

  2. 进击的Python【第九章】:paramiko模块、线程与进程、各种线程锁、queue队列、生产者消费者模型

    一.paramiko模块 他是什么东西? paramiko模块是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接. 先来个实例: import param ...

  3. 2.Python进程间的通信之队列(Queue)和生产者消费者模型

    一.队列 1.1 概念介绍-----multiprocess.Queue 创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. Queue([maxsize] ...

  4. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

  5. [并发编程 - socketserver模块实现并发、[进程查看父子进程pid、僵尸进程、孤儿进程、守护进程、互斥锁、队列、生产者消费者模型]

    [并发编程 - socketserver模块实现并发.[进程查看父子进程pid.僵尸进程.孤儿进程.守护进程.互斥锁.队列.生产者消费者模型] socketserver模块实现并发 基于tcp的套接字 ...

  6. 4-[多进程]-互斥锁、Queue队列、生产者消费者

    1.互斥锁 (1)为什么需要互斥锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如下 #并发运行,效率 ...

  7. 【python】-- 队列(Queue)、生产者消费者模型

    队列(Queue) 在多个线程之间安全的交换数据信息,队列在多线程编程中特别有用 队列的好处: 提高双方的效率,你只需要把数据放到队列中,中间去干别的事情. 完成了程序的解耦性,两者关系依赖性没有不大 ...

  8. python_way ,day11 线程,怎么写一个多线程?,队列,生产者消费者模型,线程锁,缓存(memcache,redis)

    python11 1.多线程原理 2.怎么写一个多线程? 3.队列 4.生产者消费者模型 5.线程锁 6.缓存 memcache redis 多线程原理 def f1(arg) print(arg) ...

  9. 进程间通信IPC机制和生产者消费者模型

    1.由于进程之间内存隔离,那么要修改共享数据时可以利用IPC机制 我们利用队列去处理相应数据 #管道 #队列=管道+锁 from multiprocessing import Queue # q=Qu ...

随机推荐

  1. Running Code on a Thread Pool Thread_翻译

    The previous lesson showed you how to define a class that manages thread pools and the tasks that ru ...

  2. Docker笔记(五):整一个自己的镜像

    原文地址:http://blog.jboost.cn/2019/07/17/docerk-5.html 获取镜像的途径有两个,一是从镜像仓库获取,如官方的Docker Hub,二是自定义.上文已经介绍 ...

  3. GitHub使用整理——从开始到上传项目

    前期准备 首先是github官网: https://github.com/ 下载github工具: https://git-for-windows.github.io/ 进入github创建一个新的项 ...

  4. Python 3.5学习笔记(第一章)

    本章内容: 1.安装python 3.5 和 PyCharm 社区版 2.第一个python程序 3.变量 4.字符编码 5.用户输入 6.字符串格式化输出 7.if .else .elif 8.fo ...

  5. HBase的优化

    HBase的优化 高可用 在 HBase 中 Hmaster 负责监控 RegionServer 的生命周期,均衡 RegionServer 的负载,如果 Hmaster 挂掉了,那么整个 HBase ...

  6. Docker 工作原理及容器化简易指南

    Docker 非常棒! 它使软件开发者无需担心配置和依赖性,在任何地方打包,发送和运行他们的应用程序.而在与 Kubernetes 相结合后,它使应用集群部署和管理变得更方便.这使得 Docker 深 ...

  7. C#编程之接口

    1.定义 接口是把公共方法和属性组合起来,以封装特定功能的一个集合.(一旦定义了接口,就可以在类中实现它.这样类就可以支持接口所指定的所有属性和成员) 注意1:接口不能单独存在.不能像实例化一个类那样 ...

  8. spark 源码分析之十九 -- DAG的生成和Stage的划分

    上篇文章 spark 源码分析之十八 -- Spark存储体系剖析 重点剖析了 Spark的存储体系.从本篇文章开始,剖析Spark作业的调度和计算体系. 在说DAG之前,先简单说一下RDD. 对RD ...

  9. <java程序大集合>

    1.以下关于开发java程序的描述错误的是(). A.开发java程序的步骤包括:编写源程序,编译,运行 B.编写的java源程序文件使用.java作为扩展名 C:java源文件经编译后,生成后娺为. ...

  10. 私有网络(VPC)概述

    1 什么是私有网络(VPC) 私有网络是一块可用户自定义的网络空间,您可以在私有网络内部署云主机.负载均衡.数据库.Nosql快存储等云服务资源.您可自由划分网段.制定路由策略.私有网络可以配置公网网 ...