Python学习--17 进程和线程
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
进程
fork调用
通过fork()
系统调用,就可以生成一个子进程。
下面先了解下关于fork()
的相关知识:
Unix/Linux操作系统提供了一个
fork()
系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用
getppid()
就可以拿到父进程的ID。
Python里的os
模块封装了常见的系统调用,包括fork()
:
# coding: utf-8
import os
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid == 0:
print('I am child Process %s , my parent is %s ' % (os.getpid(), os.getppid()))
else:
print('I am parent Process %s , my child is %s ' % (os.getpid(), pid))
上面代码无法运行在Windows系统上(没有fork调用),需要运行在Unix/Linux操作系统。输出:
# ./user_process.py
Process (25464) start...
I am parent Process 25464 , my child is 25465
I am child Process 25465 , my parent is 25464
multiprocessing
虽然fork()
调用无法在Windows调用,但Python也提供了跨平台的多进程支持。使用multiprocessing
即可创建跨平台多进程:
# coding: utf-8
from multiprocessing import Process
import os
def run_proc(name):
print('Child Process %s %s is running...' % (name, os.getpid()))
if __name__ == '__main__':
print('Parent Process %s is running...' % os.getpid() )
p = Process(target=run_proc, args=('testProcess', ))
p.start()
p.join()
输出:
Parent Process 10488 is running...
Child Process testProcess 3356 is running...
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例,用start()
方法启动。jonin()
方法用于等待子进程结束后再继续往下运行,相当于阻塞了进程的异步执行。
这里的if __name__ == '__main__'
用于仅允许在命令行下直接运行。如果是另一个模块引入该文件,里面的代码不会执行。
在python中,当一个module作为整体被执行时,
moduel.__name__
的值将是__main__
;而当一个 module被其它module引用时,module.__name__
将是module自己的名字,当然一个module被其它module引用时,其本身并不需要一个可执行的入口main
了。
上面的多进程创建代码风格与后文讲的创建多线程很相似。
进程池
如果要启动大量的子进程,可以用进程池(Pool)的方式批量创建子进程:
user_process_pool.py
# coding: utf-8
from multiprocessing import Pool
import os,time,random
def run_task(name):
print('Run task %s' % name)
s_start = time.time()
time.sleep(random.random())
s_end = time.time()
print('Task %s run %.2f sec' % (name, s_end - s_start))
if __name__ == '__main__':
print('Parent Process %s is running...' % os.getpid())
p = Pool(5)
for i in range(5):
p.apply_async(run_task, args=(i,))
print('all subProcess will running...')
p.close()
p.join()
print('all subProcess running ok')
输出:
Parent Process 10576 is running...
all subProcess will running...
Run task 0
Run task 1
Run task 2
Run task 3
Run task 4
Task 3 run 0.16 sec
Task 1 run 0.31 sec
Task 4 run 0.38 sec
Task 2 run 0.44 sec
Task 0 run 0.89 sec
all subProcess running ok
如果修改Pool()
的参数为1,看下输出:
Parent Process 6252 is running...
all subProcess will running...
Run task 0
Task 0 run 0.77 sec
Run task 1
Task 1 run 0.22 sec
Run task 2
Task 2 run 0.73 sec
Run task 3
Task 3 run 0.54 sec
Run task 4
Task 4 run 0.02 sec
all subProcess running ok
说明有Pool进程池的数量有多少,就可以最多同时运行多少个进程。如果不设置参数,Pool的默认大小是CPU的核数。
对Pool对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process了。
外部子进程
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess
模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出:
# coding:utf-8
import subprocess
r = subprocess.call(['ping', 'www.python.org'])
print(r)
然后运行:
$ python user_subprocess.py
正在 Ping www.python.org [151.101.72.223] 具有 32 字节的数据:
来自 151.101.72.223 的回复: 字节=32 时间=189ms TTL=53
来自 151.101.72.223 的回复: 字节=32 时间=183ms TTL=53
来自 151.101.72.223 的回复: 字节=32 时间=192ms TTL=53
来自 151.101.72.223 的回复: 字节=32 时间=192ms TTL=53
151.101.72.223 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 183ms,最长 = 192ms,平均 = 189ms
0
相当于命令行运行:
ping www.python.org
进程间通信
进程间肯定需要互相通信的。这里我们使用队列来实现一个简单的例子:
# coding: utf-8
from multiprocessing import Process,Queue
import os,time
def write(q):
print('write Process %s is running... ' % os.getpid())
for x in ['python', 'c', 'java']:
q.put(x)
print('write to Queue : %s' % x)
def read(q):
print('read Process %s is running... ' % os.getpid())
while True:
r = q.get(True)
print('read from Queue : %s' % r)
pass
if __name__ == '__main__':
print('MainProcess %s is running...' % os.getpid())
q = Queue()
p1 = Process(target=write, args=(q,))
p2 = Process(target=read, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
# p2.terminate()
输出:
MainProcess 9680 is running...
write Process 10232 is running...
write to Queue : python
write to Queue : c
write to Queue : java
read Process 8024 is running...
read from Queue : python
read from Queue : c
read from Queue : java
由于p2进程里是死循环,默认执行完毕后程序不会退出,可以使用p2.terminate()
进程强行退出。
线程
Python的标准库提供了两个模块:_thread
和threading
,_thread
是低级模块,threading
是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。
创建线程
启动一个线程就是把一个函数传入并创建threading.Thread()
实例,然后调用start()
开始执行:
# coding: utf-8
import threading,time
def test():
print('Thread %s is running... ' % threading.current_thread().name)
print('waiting 3 seconds... ')
time.sleep(3)
print('Hello Thread!')
print('Thread %s is end. ' % threading.current_thread().name)
print('Thread %s is running... ' % threading.current_thread().name)
t = threading.Thread(target = test, name = 'TestThread')
t.start()
t.join()
print('Thread %s is end. ' % threading.current_thread().name)
输出:
Thread MainThread is running...
Thread TestThread is running...
waiting 3 seconds...
Hello Thread!
Thread TestThread is end.
Thread MainThread is end.
t.start()
用于启动线程。t.join()
的作用是等待线程执行完毕,否则会不等待线程执行完毕就执行下面的代码了,因为线程执行是异步的。
主线程实例的名字叫MainThread
,子线程的名字在创建时指定,这里我们用TestThread
命名子线程。名字仅仅在打印时用来显示,完全没有其他意义。
线程锁
多线程与多进程最大的不同就是:对于同一个变量,对于多个线程是共享的,但多进程里每个进程各自会复制一份。
所以,在多线程里,最大的一个隐患就是多个线程同时改一个变量,可能就把变量内容改乱了。看下面例子如何改乱一个变量:
# coding:utf-8
import threading
amount = 0
def changeValue(x):
global amount
amount = amount + x
amount = amount - x
# 批量运行改值
def batchRunThread(x):
for i in range(100000):
changeValue(x)
# 创建2个线程
t1 = threading.Thread(target=batchRunThread, args=(5,), name = 'Thread1')
t2 = threading.Thread(target=batchRunThread, args=(15,), name = 'Thread2')
t1.start()
t2.start()
t1.join()
t2.join()
print(amount)
正常情况下会输出0。但是运行多次,发现结果不一定是0。由于线程的调度是由操作系统决定的,当t1、t2线程交替执行时,只要循环次数足够多,结果就可能被改乱了。
原因是高级语言的一条语句执行在CPU执行是多个语句,即使是一个简单的计算:
amount = amount + x
CPU会执行下列运算:
1、计算amount + x,存入临时变量中;
2、将临时变量的值赋给amount。
想要解决这个问题,就要使用线程锁:线程执行changeValue()
时先获取锁,这时候其它线程想要执行changeValue()
,想要等待之前那个线程释放锁。Python里通过threading.Lock()
来实现。
# coding:utf-8
import threading
lock = threading.Lock()
amount = 0
def changeValue(x):
global amount
amount = amount + x
amount = amount - x
# 批量运行改值
def batchRunThread(x):
for i in range(100000):
lock.acquire() # 获得锁
try:
changeValue(x)
finally:
lock.release() # 释放锁
# 创建2个线程
t1 = threading.Thread(target=batchRunThread, args=(5,), name = 'Thread1')
t2 = threading.Thread(target=batchRunThread, args=(15,), name = 'Thread2')
t1.start()
t2.start()
t1.join()
t2.join()
print(amount)
这时候不管循环多少次,输出的结果永远是0。
想要注意的是,获得锁后一定要记得释放,否则其它线程一直在等待,就成了死锁。这里使用try...finally...
保证最后一定会释放锁。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。
如果锁使用不当,也可能造成死锁,程序无法正常运行。
1、说说进程与线程的区别与联系 - oyzway - 博客园
http://www.cnblogs.com/way_testlife/archive/2011/04/16/2018312.html
2、进程与线程的一个简单解释 - 阮一峰的网络日志
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
Python学习--17 进程和线程的更多相关文章
- Python学习--18 进程和线程
线程是最小的执行单元,而进程由至少一个线程组成.如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间. 进程 fork调用 通过fork()系统调用,就可以生成一个子进程 ...
- python学习之-- 进程 和 线程
python 进程/线程详解 进程定义:以一个整体的形式暴露给操作系统管理,它里面包含对各种资源的调用,内存的管理,网络接口的调用等等,对各种资源管理的集合,就可以叫做一个进程. 线程定义:线程是操作 ...
- day34 python学习 守护进程,线程,互斥锁,信号量,生产者消费者模型,
六 守护线程 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 需要强调的是:运行完毕并非终止运行 #1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完 ...
- python学习(十三)进程和线程
python多进程 from multiprocessing import Process import os def processFunc(name): print("child pro ...
- Python学习day38-并发编程(线程)
figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max- ...
- python中的进程、线程(threading、multiprocessing、Queue、subprocess)
Python中的进程与线程 学习知识,我们不但要知其然,还是知其所以然.你做到了你就比别人NB. 我们先了解一下什么是进程和线程. 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CP ...
- VC++学习之进程和线程的区别
VC++学习之进程和线程的区别 一.进程 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格.内存空间.磁盘 ...
- JUC学习笔记——进程与线程
JUC学习笔记--进程与线程 在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的进程与线程部分 我们会分为以下几部分进行介绍: 进程与线程 并发与并行 同步与异步 线程详解 进程与线程 ...
- Python 中的进程、线程、协程、同步、异步、回调
进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生? 一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说 ...
随机推荐
- 严重: Servlet.service() for servlet jsp threw exception java.lang.IllegalStateException: getOutputStream() has already been called for this response
严重: Servlet.service() for servlet jsp threw exception java.lang.IllegalStateException: getOutputS ...
- Postman 测试web接口(推荐)
- <转>SQL的执行顺序
SQL 不同于与其他编程语言的最明显特征是处理代码的顺序.在大数编程语言中,代码按编码顺序被处理,但是在SQL语言中,第一个被处理的子句是FROM子句,尽管SELECT语句第一个出现,但是几乎总是最后 ...
- c#中怎么求百分比
string Scorepercent = (lowScoreNum*1.0/ ScoreNum).ToString("P");//百分比 ToString("P&quo ...
- CABasicAnimation 基本动画 分类: ios技术 2015-07-16 17:10 132人阅读 评论(0) 收藏
几个可以用来实现热门APP应用PATH中menu效果的几个方法 +(CABasicAnimation *)opacityForever_Animation:(float)time //永久闪烁的动画 ...
- libusb 开发者指南-牛胜超(转)
源:libusb 开发者指南 libusb Developers Guidelibusb 开发者指南 原作者:Johannes Erdfelt翻译者:牛胜超 Table of Contents目录 P ...
- 配置Linux Kernel时make menuconfig执行流程分析
在编译内核前,一般是根据已有的配置文件(一般在内核根目录下的arch/arm/configs/文件夹下,把该目录下的xxx_defconfig文件拷贝到内核根目录下,并重命名为.config)来 ...
- DialogFragment学习笔记
创建DialogFragment 跟通常的创建Fragment差不多,XML,继承DialogFragment,复写onCreateView() public View onCreateView(La ...
- 【转】IntentService的原理及使用
在Android开发中,我们或许会碰到这么一种业务需求,一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功.那么,利用几个子线程顺序执行是可以达到这个目的的,但是每个线 ...
- 详细解析Linux scp命令的应用
详细解析Linux scp命令的应用 Linux命令有人统计说是有4000多个,Linux scp命令是用于Linux之间复制文件和目录,这里详细介绍scp命令使用和参数. AD: Linux scp ...