Python的进程、线程和threading模块
(注:本文部分内容摘自互联网,由于作者水平有限,不足之处,还望留言指正。)
怀念在学校念书的时候,我不小心触碰到了错误,老师会说:你错了;而我却总是倔强得以为自己没错。我的内心是不屑的,直到在真理面前低头。
毕业后,有时其实快要进入误区(或者刚刚迈入一步),而自己却冥冥中意识到时。问大家:那个啥事我是不是做错了,该怎么抢救一下。“不,你没错”。此时此刻,我的内心却是一顿的紧迫感和无助感。直到。。。直到。。。在错误面前下跪。
哎呀,生活好讨厌,不过没关系,幸好我机智。我就是我,那个用挖掘机炒菜的男人。
好了,下面开始我们的表演,show time。
在python界一直有着一个古老的传说,那就是python的多线程是鸡肋,那么这个传说的信度到底有多少呢?如果我们的代码是CPU密集型(涉及到大量的计算),多个线程的代码很有可能是线性执行的,所以这种情况下多线程是鸡肋,效率可能还不如单线程,因为有context switch(其实就是线程之间的切换和线程的创建等等都是需要消耗时间的);但是:如果是IO密集型,多线程可以明显提高效率。例如制作爬虫,绝大多数时间爬虫是在等待socket返回数据。这个时候C代码里是有release GIL的,最终结果是某个线程等待IO的时候其他线程可以继续执行。
一、进程
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。执行中的程序是进程。
二、线程
线程,有时被称为轻量级进程,是程序执行流的最小单元(操作系统最小的调度单位)。线程是属于进程的,我们平时写的简单程序,是单线程的,多线程和单线程的区别在于多线程可以同时处理多个任务。
三、协程
协程是一种用户态的轻量级线程。如果说多进程对于多CPU,多线程对应多核CPU,那么事件驱动和协程则是在充分挖掘不断提高性能的单核CPU的潜力。我们既可以利用异步优势,又可以避免反复系统调用,还有进程切换造成的开销,这就是协程。协程也是单线程,但是它能让原来要使用异步+回调方式写的非人类代码,可以用看似同步的方式写出来。它是实现推拉互动的所谓非抢占式协作的关键。对于python来说,由于python多线程中全局解释器导致的同时只能有一个线程访问cpu,所以对协程需求就相比于其他语言更为紧迫。
四、进程、线程与协程的关系
IO密集型应用: 多进程->多线程->事件驱动->协程
CPU密集型应用:多进程-->多线程
调度和切换的时间:进程 > 线程 > 协程
关于进程与线程的关系,网上有一副很好的解释:
1. 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2. 假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,
单个CPU一次只能运行一个任务。
3. 进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
4. 一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。
5. 车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用
这些共享内存。
6. 可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程
使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
7. 一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就
叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
8. 还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,
只能供给固定数目的线程使用。
9. 这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道
必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
10. 不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,
且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11. 操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
五、threading模块的介绍
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
import threading #导入模块
import time
def show(arg):
time.sleep(3)
print(arg) if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=show,args=(i,)) #target是函数返回值,args后依次输入变量,最后一个变量后要加,
t.start() #启动线程
#启动了10个线程
· start 线程准备就绪,等待CPU调度
· setName 为线程设置名称
· getName 获取线程名称
· setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
· join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
· run 线程被cpu调度后自动执行线程对象的run方法
加入主线程的情况:
import threading #导入模块
import time
def show(arg):
print('hello%d'%arg)
time.sleep(3)
print(arg) if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=show,args=(i,)) #target是函数返回值,args后依次输入变量,最后一个变量后要加,
t.start() #启动线程
#启动了10个子线程
print('qwer') #主线程
运行结果:

子线程启动后,直接进行主线程(而不是等子线程执行完再执行主线程)。
如果要子线程执行完,再执行主线程,需要加入jion函数。
import threading #导入模块
import time
l1 = []
def show(arg):
print('hello%d'%arg)
time.sleep(3)
print(arg) if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=show,args=(i,)) #target是函数返回值,args后依次输入变量,最后一个变量后要加,
t.start() #启动线程
l1.append(t)
for t in l1:
t.join() #等待每一个线程执行完毕
#启动了10个线程
print('qwer')
介绍一下守护线程deamon:
import threading #导入模块
import time
def show(arg):
print('hello%d'%arg)
time.sleep(3)
print(arg) if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=show,args=(i,)) #target是函数返回值,args后依次输入变量,最后一个变量后要加,
t.setDaemon(True) #守护进程,主线程执行完就退出
t.start() #启动线程
#启动了10个线程
print('qwer')
运行结果:

主线程执行完毕后,子线程就都停掉了,show函数里sleep后面的就不会在执行。
六、线程锁(好像Python3中不用吧,我还要确认一下)
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。
import time
import threading
def addNum():
global num #在每个线程中都获取这个全局变量
print('--get num:',num )
time.sleep(1)
num -=1 #对此公共变量进行-1操作
num = 100 #设定一个共享变量
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
t.join()
print('final num:', num )
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。

说一下四核CPU上多进程是怎么走的:
对于四核的cpu,如果四个线程打到了四个核,同时启动,却不能执行,必须GIL锁才能执行。所以同一时间只能执行一个线程。加锁的目的:一个线程不被多个CPU执行。
通俗:四个核相当于四个卫生间,四个工人A、B、C、D(线程)分别进入了四个卫生间,他们想要小便,但是为了害羞(避免多次执行),需要门上加一把锁(GIL锁,系统只有一把锁)。A上锁后,开始小便;小便2秒(大约CPU的100行底层命令);把锁给B,B开始小便(这个时候A、C、D憋住)。然后再给C,再给D。。。。。。所以,PYTHON同一时间只能执行一个线程。
如果A、B线程属于同一进程,就有可能出错。比如for循环i=i+1(初始是0),
如果A将i等于0赋值给寄存器,还没执行完,锁的时间到了;这时B得到的i值仍然是0,刚好在锁的时间内执行完,i的值变成1。然后锁到A,而寄存器里的值还是0,得到结果仍然是1。执行两次,结果依旧是1。(Python3.0以上已经解决这个问题。)
嗯,就写这些吧。
国庆八天,都在家里抱着电脑不能自拔,
也是美滋滋吧。
Python的进程、线程和threading模块的更多相关文章
- {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器
Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...
- python全栈开发 * 进程池,线程理论 ,threading模块 * 180727
一.进程池 (同步 异步 返回值) 缺点: 开启进程慢 几个CPU就能同时运行几个程序 进程的个数不是无线开启的 应用: 100个任务 进程池 如果必须用多个进程 且是高计算型 没有IO型的程序 希望 ...
- <python的线程与threading模块>
<python的线程与threading模块> 一 线程的两种调用方式 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而thre ...
- *****Python之进程线程*****
Python之进程线程 Python的threading模块 并发编程: 操作系统:位于底层硬件与应用软件之间的一层. 工作方式:向下管理硬件,向上提供接口. 进程:资源管理单位(容器) 线程:最 ...
- 线程与threading模块
线程 进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位.在单个进程中同时运行多个线程完成不同的工作,称为多线程. 同一进程内的多个线程是共享该进程的资源. 创建新的线程开销 ...
- Python 浅析线程(threading模块)和进程(process)
线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 进程与线程 什么 ...
- Python之网路编程利用threading模块开线程
一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1 1.创建线程的开销比创建进程的开销 ...
- Python的并发并行[1] -> 线程[0] -> threading 模块
threading模块 / threading Module 1 常量 / Constants Pass 2 函数 / Function 2.1 setprofile()函数 函数调用: thread ...
- python进阶-------进程线程(二)
Python中的进程线程(二) 一.python中的"锁" 1.GIL锁(全局解释锁) 含义: Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(G ...
随机推荐
- php 快速读取文件夹下文件列表
在读取某个文件夹下的内容的时候 以前是使用 opendir readdir结合while循环过滤 . ..当前文件夹和父文件夹来操作的. 代码如下: 然后偶然发现了有scandir函数 可以扫描文件夹 ...
- iOS开发之--获取验证码倒计时及闪烁问题解决方案
大家在做验证码的时候一般都会用到倒计时,基本上大家实现的方式都差不多,先贴出一些代码来.. -(void)startTime{ __block ; //倒计时时间 dispatch_queue_t q ...
- Windows游戏编程大师技巧之三角形填充
一.三角形的种类 三角形一般可以分为如下的四种类型(这四种类型是对于计算机来说的,不是数学意义上的分类): 平顶三角形:就是在计算机中显示的上面两个顶点的Y坐标相同. 平底三角形:就是在计算机中显示的 ...
- 【BZOJ2560】串珠子 状压DP+容斥
[BZOJ2560]串珠子 Description 铭铭有n个十分漂亮的珠子和若干根颜色不同的绳子.现在铭铭想用绳子把所有的珠子连接成一个整体. 现在已知所有珠子互不相同,用整数1到n编号.对于第i个 ...
- MAC OSX安装多个版本的JAVA(jdk jre通用)
MAC自带的jdk1.6是苹果公司自己修改的jdk版本,被广泛应用于各种mac软件,具有不可替代性:同时,java1.7和1.8有时也需要用到.因此,在mac上安装.使用多个版本的java具有重要意义 ...
- 面试之四:JVM内存区域分配
1.程序计数器(线程私有)[不会OOM] 记录线程执行的代码位置,每个线程各自独有. 2.栈:虚拟机栈和本地方法栈(线程私有)[会OOM和StackOverflow] 虚拟机栈 每个JAVA方法在执行 ...
- Code Forces 26C Dijkstra?
C. Dijkstra? time limit per test 1 second memory limit per test 64 megabytes input standard input ou ...
- SQL中的循环获取根节点
方法一: DECLARE @employeeid INT; set @employeeid = 8115; DECLARE @id INT; DECLARE @pid int; DECLARE @le ...
- git之merge和rebase的区别
merge合并 # merge操作 第一步: # 先创建一个目录,在主分支提交3个txt文件 [root@luchuangao]# mkdir oldboy [root@luchuangao]# gi ...
- Redis常见操作
1. 对于key的所有操作 del key1 key2 … keyn 作用:删除1个或者多个键返回值:不存在的key忽略掉,返回真正删除的key的数量 rename key newkey 作用:给ke ...