文章目录

  • 进程和线程
    • 一、基础知识
    • 二、多进程(multiprocessing)
      • 1、初体验
      • 2、Pool(进程池)
        • (1)非阻塞
        • (2)阻塞
        • (4)代码解读
        • (3)分析
      • 3、子进程
        • (1)开启子进程
        • (2)添加输入——communicate()方法
      • 4、进程间通信
    • 三、多线程
      • 1、启动
      • 2、Lock
      • 3、多核CPU
      • 4、ThreadLocal
    • 四、进程 vs. 线程
      • 1、多任务实现
      • 2、线程切换
      • 3、计算密集型 vs. IO 密集型
      • 4、异步IO
    • 五、分布式进程

进程和线程

对于操作系统来说,一个任务就是一个进程(Process)

在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,将进程内的这些“子任务”称为线程(Thread)

==》如何同时执行多个任务?

  • 方法1:多进程模式。 启动多个进程,每个进程虽然只有一个线程,但是多个进程可以一块执行多个任务。
  • 方法2:多线程模式。 启动一个进程,在一个进程内启动多个线程,则多个线程可以一块执行多个任务。
  • 方法3:多进程+多线程模式。启动多个进程,每个进程再启动多个线程==》模型复杂,很少采用。
  • 注意:还需考虑相互通信和协调、同步、数据共享的问题。

一、基础知识

Unix/Linux系统中的 fork() 系统调用,fork() 调用一次,返回两次。(操作系统自动将当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程内返回。)

**子进程永远返回 0,而父进程返回子进程的 ID。**子进程调用 getppid() 就可以获得父进程的 ID。

import osprint('Process (%s) start...' % os.getpid())
## 仅在Unix/Linux系统下
pid = os.fork()
if pid == 0:print("I am child process (%s) and my parent is %s." % (os.getpid(), os.getppid()))
else:print("I (%s) just created a child process (%s)." % (os.getpid(), pid))

输出结果

Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.
  • os.getpid()——获取当前进程的 ID
  • os.getppid()——获得当前进程的父进程的 ID

==》fork 调用可以在一个进程在接到新任务时就可以复制出一个子进程来处理新任务。

二、多进程(multiprocessing)

模块:multiprocessing——跨平台

1、初体验

Process 类来代表一个进程对象,创建实例对象时,需要传入执行函数和执行函数的参数;
start()方法启动进程实例;
join()方法可以等待子进程结束后再继续往下执行,常用于进程间的同步。

from multiprocessing import Process
import os# 子进程要执行的代码
def run_proc(name):print('Run child process %s (%s)...' % (name, os.getpid()))if __name__ == "__main__":print('Parent process %s.' % os.getpid())## 创建一个进程对象 p## 参数:执行函数、执行函数的参数p = Process(target=run_proc, args=('test',))print('Child process will start.')# 启动子进程 pp.start()p.join()   # 常用于进程间的同步print('Child process end.')

输出结果

Parent process 10808.
Child process will start.
Run child process test (13692)...
Child process end.

2、Pool(进程池)

用进程池(Pool)的方式批量创建子进程。

Pool 的默认大小是 CPU 的核数

(1)非阻塞

import multiprocessing, os
import timedef func(msg):print("msg:", msg, "(%s)" % os.getpid())start = time.time()time.sleep(3)end = time.time()print("Task %s end %f" % (msg, (end - start)))if __name__ == '__main__':print('Parent process %s.' % os.getpid())pool = multiprocessing.Pool(processes = 3)for i in range(3):msg = "hello %d" % (i)pool.apply_async(func, (msg,))pool.close()pool.join()print("Sub-process(es) done.")

输出结果

Parent process 1000.
msg: hello 0 (4416)
msg: hello 1 (14240)
msg: hello 2 (828)
Task hello 0 end 3.000664
Task hello 1 end 3.000952
Task hello 2 end 3.000939
Sub-process(es) done.

(2)阻塞

import multiprocessing, time, osdef func(msg):print("msg:", msg, "(%s)" % os.getpid())start = time.time()time.sleep(3)end = time.time()print("Task %s end %f" % (msg, (end - start)))
if __name__ == '__main__':print('Parent process %s.' % os.getpid())pool = multiprocessing.Pool(processes = 3)for i in range(3):msg = "hello %d" % (i)pool.apply(func, (msg,))pool.close()pool.join()print("Sub-process(es) done.")

输出结果

Parent process 244.
msg: hello 0 (10280)
Task hello 0 end 3.000280
msg: hello 1 (15200)
Task hello 1 end 3.000413
msg: hello 2 (10140)
Task hello 2 end 3.000886
Sub-process(es) done.

(4)代码解读

对 Pool 对象调用 join() 方法会等待所有子进程执行完毕,调用 join() 之前必须先调用 close(),调用 close()之后就不能继续添加新的 Process 了。

(3)分析

区别主要是 apply_async和 apply函数,前者是非阻塞的,后者是阻塞。非阻塞多个子进程可以同时进行,而阻塞子进程依次进行。

3、子进程

子进程是一个外部进程,需要控制其输入和输出。主要功能是执行外部的命令和程序。
==》模块:subprocess

subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

使用subprocess包中的函数创建子进程的时候,要注意:

  • 在创建子进程之后,父进程是否暂停,并等待子进程运行。
  • 函数返回什么
  • 当returncode不为0时,父进程如何处理。

(1)开启子进程

  • subprocess.call()
  • subprocess.check_call()
  • subprocess.check_output()

参数:命令字符串,eg:([‘ping’,‘www.baidu.com’,’-c’,‘3’]) 或 (“ping www.baidu.com -c 3”) 两种形式。在Windows环境下,最好添加 shell=True 参数,使得可以顺利地执行dos命令。

区别:返回值。子进程的执行返回码;若返回码是0则返回0,否则出错的话raise起CalledProcessError,可以用except处理之;若返回码是0则返回子进程向stdout输出的结果,否则也raise起CalledProcessError。

三种方法均会让父进程挂起等待,在子进程结束之前,父进程不会继续执行下去。

本质:对 subprocess.Popen 方法的封装,Popen 开启的子进程不会让父进程等待其完成的,除非调用 wait() 方法。

import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup','www.python.org'])
print('Exit coe:' ,r)

结果输出

$ nslookup www.python.org
��Ȩ��Ӧ��:
������:  UnKnown
Address:  192.168.43.1����:    dualstack.python.map.fastly.net
Addresses:  2a04:4e42:6::223151.101.24.223
Aliases:  www.python.orgExit coe: 0

(2)添加输入——communicate()方法

## 有输入的子进程
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('gbk'))
# returncode 子进程的退出状态
print('Exit code:', p.returncode)

结果输出

$ nslookup
默认服务器:  UnKnown
Address:  192.168.43.1> > 服务器:  UnKnown
Address:  192.168.43.1python.org    MX preference = 50, mail exchanger = mail.python.org
>
Exit code: 0

stdin, stdout 和 stderr:指定了执行程序的标准输入,标准输出和标准错误的文件句柄。它们的值可以是PIPE, 一个存在的文件描述符(正整数),一个存在的文件对象,或 None。

4、进程间通信

多种机制:Queue、Pipes等方式交换数据。

示例:Queue为例,在父进程中创建两个子进程,一个往 Queue 里写数据,一个从 Queue 里读数据。

from multiprocessing import Process, Queue
import os, time, random# 写数据进程执行的代码
def write(q):print('Process to write: %s' % os.getpid())for value in ['A', 'B', 'C']:print('Put %s to queue...' % value)q.put(value)time.sleep(random.random())# 读数据进程执行的代码
def read(q):print('Process to read: %s' % os.getpid())while True:value = q.get(True)print('Get %s from queue.' % value)if __name__ == '__main__':# 父进程创建 Queue,并传给各个子进程q = Queue()pw = Process(target=write, args=(q,))pr = Process(target=read, args=(q, ))# 启动子进程 pw,写入pw.start()# 启动子进程 pr,读取pr.start()# 等待 pw 结束pw.join()# pr 进程里是死循环,无法等待其结束,只能强行终止pr.terminate()

结果输出

Process to write: 3564
Put A to queue...
Process to read: 2500
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

三、多线程

一个进程内包含若干线程(至少有一个线程)。

线程是操作系统直接支持的执行单元 ==》高级语言内置多线程支持。Python中为真正的 Posix Thread

模块:_thread 和 threading

  • _thread:低级模块
  • threading:对_thread的封装,最常用。

1、启动

启动一个线程就是把一个函数传入并创建 Thread 实例,然后调用 start() 开始执行。

import time, threading# 新线程执行的代码
def loop():print('Thread %s is running...' % threading.current_thread().name)n = 0while n < 5:n = n + 1print('Thread %s >>> %s' % (threading.current_thread().name, n))time.sleep(1)print('Thread %s ended.' % threading.current_thread().name)print('Thread %s is running...' % threading.current_thread().name)
## 创建子线程
t = threading.Thread(target=loop, name='LoopThread')
## 启动子线程
t.start()
t.join()
print('Thread %s ended.' % threading.current_thread().name)

结果输出

Thread MainThread is running...
Thread LoopThread is running...
Thread LoopThread >>> 1
Thread LoopThread >>> 2
Thread LoopThread >>> 3
Thread LoopThread >>> 4
Thread LoopThread >>> 5
Thread LoopThread ended.
Thread MainThread ended.

分析

  • 任何进程默认就会启动一个线程(主线程,name:MainThread),主线程又可以启动新的线程。
  • threading.current_thread()函数,返回当前线程的实例。
  • 子线程的名字在创建时指定,eg:LoopThread。若不指定,则自动给线程命名为 Thread-1、Thread-2…

2、Lock

进程与线程最大的区别

  • 多进程中,同一变量,各自有一份拷贝存于每个进程,互不影响;
  • 多线程中,所有变量都由所有线程共享。==》任何一个变量都可以被任何一个线程修改。

原因:高级语言的一条语句在CPU执行时是若干条语句,而执行这几条语句中时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。
==》解决方法:threading.Lock() 函数。

import time, threading# 假定这是你的银行存款
balance = 0
lock = threading.Lock()def run_thread(n):for i in range(10000000):# 先要获取锁lock.acquire()try:# 放心地改吧change_it(n)finally:# 改完了一定要释放锁lock.release()def change_it(n):# 先存后取,结果应该为 0global balancebalance = balance + nbalance = balance - nt1 = threading.Thread(target=run_thread, args=(5, ))
t2 = threading.Thread(target=run_thread, args=(8, ))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

结果输出:0
分析:当多个线程同时执行 lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

注意:获得锁的线程使用完要释放锁,否则会造成死线程,推荐使用 try…finally 方式。

缺点

  • 阻止多线程并发的执行:包含锁的某段代码实际只能单线程模式执行,效率低;
  • 存在多个锁时,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

3、多核CPU

多核CPU遇到死循环时:

  • CPU使用率:一个死循环线程会100%占用一个CPU;两个则会占到200%CPU(两个CPU核心)==》要想把 N 核 CPU 的核心全部跑满,就必须启动 N 个死循环线程。

在Python中,当解释器执行代码时,有个 GIL锁(Global Interpreter Lock)。任何 Python 线程执行前,先必须获得GIL锁,然后,每执行100条字节码,解释器就自动释放 GIL 锁,让其他的线程有机会执行。
==》多线程在Python中只能交替运行,也只能用到一个核。
==》可以使用多线程,但不能有效利用多核,除非通过C扩展实现。
==》Python 可利用多进程实现多任务。多个进程有自己独立的 GIL 锁,互不影响。

4、ThreadLocal

ThreadLocal 最常用于 为每个线程绑定一个数据库连接、HTTP请求、用户身份信息等。
==》一个线程的所有调用到的处理函数都可以访问这些资源。

import threading# 创建全局 ThreadLocal 对象
local_school = threading.local()def process_student():# 获取当前线程关联的 student,# 每个Thread对它都可读写student属性,互不影响std = local_school.studentprint('Hello, %s (in %s)' % (std, threading.current_thread().name))def process_thread(name):# 绑定 ThreadLocal 的 studentlocal_school.student = nameprocess_student()t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob', ), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

结果输出

Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)

四、进程 vs. 线程

1、多任务实现

通常设计 Master-Worker 模式,Master 负责分配任务,Worker 负责执行任务。
==》一个 Master、多个 Worker

若用多进程实现 Master-Worker,主进程是 Master,其他进程是 Worker;
==》稳定性高。(一个子进程崩溃了,不会影响主进程和其他子进程。当然主进程挂了所有进程就全挂了,但是Master 进程只负责分配任务,挂掉的概率低,eg:Apache)
==》创建进程的代价大。 Windows下开销巨大。

若用多线程实现 Master-Worker,主线程是 Master,其他线程是 Worker。
==》稍快,效率高(Windows下IIS服务区默认采用多线程模式)
==》致命缺点:任何一个线程挂掉都可能直接造成整个进程崩溃(所有线程共享进程的内存)。

2、线程切换

单任务模式(批处理任务模式):处理完任务A,再处理任务 B …

多任务模式:涉及到任务的切换。操作系统在切换进程或
者线程时也是一样的,它需要先保存当前执行的现场环境( CPU 寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。
==》任务过多,会造成系统处于假死的状态。

3、计算密集型 vs. IO 密集型

计算密集型任务

  • 需要大量的计算,消耗CPU资源。==》强调:代码效率(C语言)
  • 虽可以用多任务完成,但任务越多,切换所需的时间就越多,CPU执行任务的效率就越低 ==》高效:计算密集型任务同时进行的数量等于CPU的数量。

IO 密集型任务

  • 涉及网络、磁盘IO的任务均为IO 密集型任务;
  • CPU 消耗少,大多数时间(99%)是等待IO操作完成。
  • 任务越多,CPU效率越高==》强调:开发效率(Python)

4、异步IO

可实现:用单进程单线程模型执行多任务,称为事件驱动模型

五、分布式进程

Thread vs. Process

  • Process 更稳定,可分布到多台机器上;
  • Thread 最多只能分布到同一台机器的多个 CPU 上。

multiprocessing.managers 模块

  • 支持把多进程分布到多台机器上。一个服务进程为调度者,通过网络通信将任务分布到其他多个进程中。

待完善。。。

【Python】编程笔记9相关推荐

  1. Python 常用线型 + 点符号 + 颜色汇总 ∈ Python 编程笔记

    文章目录 Part.I 线型 Chap.I 基本线型 Chap.II 元组线型 Part.II 点符号 Chap.I 基本符号 Chap.II 高级符号 Part.III 颜色 Chap.I 单词或字 ...

  2. Python编程笔记6字典

    Python编程学习笔记,第6记:数据结构之字典           本节将学习:能够将相关信息关联起来的Python字典.如何定义字典.如何使用存储在字典中的信息.如何访问和修改字典中的元素.如何遍 ...

  3. Python编程笔记

    Table of Contents 1. 绪论 2. python编程概述 2.1. 知识点 2.2. 良好的编程习惯 2.3. 常见编程错误 2.4. 测试和调试提示 2.5. 移植性提示 3. 控 ...

  4. 《Python学习笔记》——南溪的python编程笔记

    1 致谢 感谢张轩老师分享的关于函数式编程的博文! 1 前言 Python中使用函数式编程也是很重要的: 2 函数 (对于函数编写的原则,我们参考了网易云音乐大前端团队的博文网易云音乐大前端团队--& ...

  5. Python 编程笔记(本人出品,必属精品)

    文章目录 Part.I 准备工作 Chap.I 下载安装 Chap.II 实现快捷键清屏 Chap.III 概念汇编 & 注意事项 Part.II 基础知识 Chap.I 快应用 Chap.I ...

  6. python编程注释_自学python编程笔记之:python的注释

    python中有两种注释: 看以下程序示例(未使用注释): 看以下程序示例(使用注释): 一.注释的作用: 增加程序的可读性,让开发者或后续接手程序进行维护修改的程序员提供可读性. 好的程序代码通常有 ...

  7. Python编程笔记(第三篇)【补充】三元运算、文件处理、检测文件编码、递归、斐波那契数列、名称空间、作用域、生成器...

    一.三元运算 三元运算又称三目运算,是对简单的条件语句的简写,如: 简单条件处理: if 条件成立:val = 1else:val = 2 改成三元运算 val = 1 if 条件成立 else 2 ...

  8. python 编程笔记

    不要老把代码全写一个文件,尽量模块化,分布式处理. 不要怕麻烦,代码啰嗦甚至重复都不是当前需要解决的问题,当前最主要的功能是实现功能.

  9. python编程题-python编程题库

    广告关闭 腾讯云双11爆品提前享,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高满返5000元! 上期题目连接:1000道python题库系列分享十一(9道)上期题目答案:? 本期 ...

  10. python程序设计题库-python编程题库

    广告关闭 2017年12月,云+社区对外发布,从最开始的技术博客到现在拥有多个社区产品.未来,我们一起乘风破浪,创造无限可能. 上期题目连接:1000道python题库系列分享十一(9道)上期题目答案 ...

最新文章

  1. win32ctypes.pywin32.pywintypes.error: (2, ‘LoadLibraryEx‘, ‘系统找不到指定的文件。‘)
  2. 青龙面板-傻妞sillyGirl xdd机器人频繁掉线解决方法
  3. python 并行计算库_Python 大规模数据存储与读取、并行计算:Dask库简述
  4. @RequestParam 注解的使用——Spring系列知识学习笔记
  5. c语言的上级步骤,数据结构 上级程序一(C语言).doc
  6. 在 Postman 中报错:Self-signed SSL certificates are being blocked 的分析与解决
  7. java中哪些可以私有化_《Java基础学习笔记》JAVA修饰符之私有化(Private)
  8. 动态列排序_Excel表格利用函数制作排序器(可依据不同字段、升降序排序)
  9. Python去除字符串中的特殊字符(包括空格)
  10. linux snmp 限制ip_Windows/Linux服务器监控软件推荐
  11. 人口流动趋势matlab,中国人口增长模型(论文).doc
  12. SAP ABAP开发视频学习(视频教程)
  13. msvcr71.dll丢失的解决方法,如何快速进行丢失修复?
  14. win10锁屏c语言,怎样在Win10锁屏界面打开任意应用程序【图文教程】
  15. 限时5折,仅剩1天!这波羊毛我撸爆~
  16. kotlin 中关键字 lateinit
  17. 苹果有arkit,android,ARKit来袭:苹果ar支持哪些设备,苹果手机怎么使用ar?
  18. 郑州73中学计算机老师,2019年关于“郑州市中学信息技术优质课评比”的通知
  19. R语言近期记录(201911)
  20. django+vue3实现websocket聊天室(群聊)

热门文章

  1. SQL语句中大于等于小于的写法
  2. XML解析技术,DOM和SAX以及STAX的区别
  3. c语言循环结构程序设计视频,第13讲:循环结构程序设计1
  4. win10 远程桌面无法连接报错
  5. python操作mysql事务提交_python连接mysql并提交mysql事务示例
  6. oracle分组_MySQL分组取TOP,实现Oracle的ROW_NUMBER函数的功能
  7. 中的 隐藏鼠标菜单_Mac移动隐藏删除顶部菜单栏图标教程
  8. 对称加密与非对称加密的区别_https原理及对称加密、非对称加密、数字证书、数字签名的含义...
  9. char* 大小_SQL Server中char, nchar, varchar和nvarchar数据类型有何区别
  10. 在长文本中当中使用正则表达式匹配限定长度范围的数字串的方法