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 ...
随机推荐
- elasticsearch性能因素总结
一:硬件方面 在预算充足的情况下.特别是一些高并发业务的搜索.硬件层面占用整个elasticsearch性能空间很大比例. 1)内存: 单实例的情况下,尽量分配32G,排序和统计都是以及内存计算的 ...
- DBA面试题及解答
一:SQL tuning 类 1:列举几种表连接方式答:merge join,hash join,nested loop Hash join散列连接是CBO 做大数据集连接时的常用方式,优化器使用两个 ...
- Struts2_day03--OGNL的#、%使用
OGNL的#.%使用 #使用 1 使用#获取context里面数据 2 演示# 操作 (1)向request域放值 (2)在页面中使用ognl获取 %使用 1 在struts2标签中表单标签 (1)在 ...
- 实现 IteratorAggregate接口
<?php class MyIterator implements Iterator{ private $var = array(); public function __construct($ ...
- Android 获取当前应用的版本号和当前系统的版本号
1.获取当前程序版本名 我们可以在AndroidManifest.xml中设置程序的版本号等,如android:versionName="1.0",那如果想在代码中获取这个版本号呢 ...
- 如何用原生js替换字符串中的某个字符(或字符串)为指定的字符串?
<html> <head><title>我的第一个 HTML 页面</title></head><script type=" ...
- style、currentStyle、getComputedStyle(不同浏览器获取css样式)区别介绍
style.currentStyle.getComputedStyle区别介绍 样式表有三种方式 内嵌样式(inline Style) :是写在Tag里面的,内嵌样式只对所有的Tag有效. 内部样 ...
- sevlet实现反盗链
有时候为了网站的版权和安全问题,我们需要为我们的网站应用设置防盗链,这样可以保证我们网站的一些资源的安全性.防盗链的主要是通过获取http的请求头referer的信息来和我们的网站地址做对比,如果相同 ...
- PHP之冒号、endif、endwhile、endfor 是什么鬼?f
解释:其实这些都是PHP的语法,只不过不常用而已,这些都是PHP流程控制的替代语法. 冒号(:)相当于是 左大括号---->{ endif.endwhile.endfor.endforeach- ...
- textarea文本输入区内实现换行
在文本间输入 即可成功换行 <textarea class="mytextarea">1.第一行 2.第二行 </textarea>