您的位置:首页 > 编程语言 > Python开发

Python学习随笔---进程和线程

2018-03-28 17:30 204 查看

进程和线程简介

线程是最小的执行单位,而进程至少由一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

python中多任务的实现方式有三种:

多进程模式

多线程模式

多线程+多
4000
进程模式


多进程模式

Linux/Unix系统提供了fork()系统调用,fork()比较特殊,普通函数调用一次,返回一次,fork()调用一次,返回两次,原因在于操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。

Python的os模块中封装了很多系统调用,其中就包括fork(),可以轻松创建多进程。

有了fork()调用,一个父进程在接到新的任务时,就可以复制出一个子进程来执行新任务,常见的Apache服务器就是由父进程监视端口,有新的http请求是时,就fork()子进程来处理新的请求。

windows系统没有fork()函数,所以os.fork()在windows系统上无法运行,mac系统是基于BSD内核(Unix的一种),所以可以运行。

multiprocessing

multiprocessing模块是python提供的适用于跨平台版本的多进程模块,提供了Process类来代表一个进程对象。

创建子进程时只需要传入一个执行函数和函数的参数,创建一个Procss实例,用start()方法启动即可;

join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

from multiprocessing import Process

p=Process(target=方法名(子进程要执行的代码),args=('方法需要传入的参数',))
p.start()
p.join()


Pool

如果要启动大量进程,可以用进程池的方式批量创建子进程。

from multiprocessing import Pol

p = Pool(4)   #可以设定同时进行的进程的数量
for i in range(5):
p.apply_async(方法名, args=(方法参数,))
p.close()
p.join()


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

子进程

很多时候,子进程并不是自身,而是一个外部进程,创建子进程后,还需要控制子进程的输入和输出。

subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。

import subprocess

#在python程序中运行命令(与在命令行运行效果一样)
r = subprocess.call(['java','-version'])
print('Exit code:',r)


运行结果:

java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
Exit code: 0


如果子进程还需要输入,则可以通过communicate()方法输入

进程间通信

操作系统提供了很多进程间的通信。Python的multiprocessing模块封装了底层的机制,提供了了Queue,Pipes等多种方法来交换数据。

在Unix/Linux系统下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要模拟出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所以,如果multiprocessing再Windows下调用失败了,要考虑是不是pickle失败了。

多线程模式

线程是操作系统直接支持的执行单元,因此,高级语言通常都内置线程的支持。Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。

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

import threading

t = threading.Thread(target=线程中需要执行的函数,name='子线程名')
#子线程名可以随意指定,仅在打印时用来显示,无意义,默认为Thread-1,Thread-2

t.start()
t.join()


任何进程默认都会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程。

Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。

主线程实例的名字叫MainThread,子线程的名字在创建时指定。

Lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每一个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所有,任何一个变量可以被任何一个线程修改,因此,线程直接共享数据最大的危险在于多个线程同时改变一个变量,把内容改乱了。

为了避免这种情况,需要给被调用的函数加一个锁,其他线程不能同时执行这个函数,只能等待,直到锁被释放。

由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。

创建锁:[b]lock=threading.Lock()[/b]

获取锁:[b]lock.acquire()[/b]

释放锁:[b]lock.release()[/b]

锁的好处是确保了某段关键代码只能由一个线程重头到尾完整执行;

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

多核CPU

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。如果一定要通过多线程利用多核,只能通过C扩展来实现。

ThreadLocal

ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。

ThreadLocal变量最常用的地方就是为每个线程绑定一个数据库,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

进程和线程对比

通常,我们会设计Master-Worker模式来实现多任务,Master负责分配任务,Worker负责执行任务。

多线程模式最大的优点是稳定,因为一个子进程崩了,不会影响主进程和其他子进程;

缺点是创建进程的代价大。

多线程模式通常比多进程快一点;

缺点是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。

是否采用多任务得考虑任务的类型,通常把任务分为计算密集型和IO密集型。

计算密集型任务要进行大量计算,消耗CPU资源,对代码运行效率要求比较高,最好用C语言编写。

涉及到网络,磁盘IO的任务都是IO密集型任务,CPU消耗很少,最好用脚本语言。

分布式进程

在Thread和Process中,优先选择Process,因为Process更稳定,且Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程发布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: