十七、并发编程基础
多任务:
同时执行多件事情。在程序中,操作系统轮流让各个任务交替执行,通过进程和线程来完成多任务。由于CPU的执行速度飞快,我们的感觉上就像多个任务同时执行一样。 真正的并行执行多任务只能在多核CPU上实现。但是,由于任务数量远远多于CPU的数量,所以,操作系统也会自动把多个任务轮流调用到每个核心上执行。
进程:
同时执行多件事情。在程序中,操作系统轮流让各个任务交替执行,通过进程和线程来完成多任务。由于CPU的执行速度飞快,我们的感觉上就像多个任务同时执行一样。 真正的并行执行多任务只能在多核CPU上实现。但是,由于任务数量远远多于CPU的数量,所以,操作系统也会自动把多个任务轮流调用到每个核心上执行。
进程:
程序编写完没有运行称之为程序。正在运行的代码就是进程。在Python3语言中,对多进程支持的是multiprocessing模块和subprocess模块。multiprocessing模块为在子进程中运行任务、通讯和共享数据,以及执行各种形式的同步提供支持。
1.join()方法中加超时的使用
from multiprocessing import Process
from time import sleep
def worker(interval):
print("work start");
sleep(interval)
print("work end");
from multiprocessing import Process
from time import sleep
def worker(interval):
print("work start");
sleep(interval)
print("work end");
if __name__ == "__main__":
p = Process(target = worker, args = (5,))
p.start() #当创建后,系统给定的名字为name:process-1....以此类推
#等待进程p终止
p.join(3) #在主进程中调用,所以主进程必须等待3秒后,无论子进程是否成功, #都进行关闭。
print("主进程结束!")
p = Process(target = worker, args = (5,))
p.start() #当创建后,系统给定的名字为name:process-1....以此类推
#等待进程p终止
p.join(3) #在主进程中调用,所以主进程必须等待3秒后,无论子进程是否成功, #都进行关闭。
print("主进程结束!")
2.进程的创建-Process子类
创建进程的方式还可以使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象,虽然在调用process的时候,调用的是start()方法,但是底层是运行run()方法,所以需要重写run方法。
3.进程池
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程。Pool的语法格式如下:
Pool([numprocess [, initializer [, initargs]]])
其中numprocess是要创建的进程数。如果省略此参数,将使用cpu_count()的值。Initializer是每个工作进程启动时要执行的可调用对象。Initargs是要传递给initializer的参数元祖。Initializer默认为None
4.进程间通信
全局变量在多个进程中不共享,进程之间的数据是独立的,默认情况下互不影响。包括子进程与子进程、子进程和主进程之间。
全局变量在多个进程中不共享,进程之间的数据是独立的,默认情况下互不影响。包括子进程与子进程、子进程和主进程之间。
5.进程间的数据传递
Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.full异常。
get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常。
如果使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue()来完成进程间的通信,而不是multiprocessing.Queue(),否则会抛出如下异常。
RuntimeError: Queue objects should only be shared between processes through inheritance
6.线程
线程也是实现多任务的一种方式,一个进程中,也经常需要同时做多件事,就需要同时运行多个‘子任务’,这些子任务就是线程。一个进程可以拥有多个并行的线程,其中每一个线程,共享当前进程的资源。(其内部也和进程一样,都是单独执行的)。进程和线程的根本区别是进程作为资源分配的单位,而线程是调度和执行的单位。进程和线程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护,而进程正相反。
7.线程共享全局变量
在一个进程内所有线程共享全局变量,多线程之间的数据共享比多进程要好。但是可能造成多个线程同时修改一个变量(即线程非安全),可能造成混乱。多进程可以通过队列的方式进行资源间的互换,多线程可以在一个进程内进行资源互换。
8.互斥锁
由上我们知道了线程是不安全的,我们引入互斥锁的概念,在容易出错的线程上加上锁,保证一次只能计算一个,就可以解决全局数据混乱的问题。线程的同步就是靠加锁进行处理lock.acquire()方法实现。
9.生产者消费者模式
随着问题解决的深入,势必会用到生产者消费者模式,两个线程(生产者和消费者)之间加入缓存,此缓存是队列,解耦性高,没有引进锁,对于解决线程同步也是一种方法。
10.ThreadLocal 变量
Python 提供了ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的。