A-A+

Python 多进程multiprocessing 多线程Thread 锁 队列 详解

2018年12月29日 10:15 汪洋大海 暂无评论 阅读 147 views 次

由于要做把一个多线程改成多进程,看一下相关方面的东西,总结一下,主要是以下几个相关的标准库

subprocess
signal
threading
multiprocessing

从Python3.2开始,标准库提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing的更高级的抽象,对编写线程池/进程池提供了直接的支持。
concurrent.futures基础模块是executor和future。

concurrent.futures 官方文档:https://docs.python.org/3/library/concurrent.futures.html

Python3 模块 - Concurrent.futures 教程:
https://www.yiibai.com/concurrency_in_python/concurrency_in_python_pool_of_processes.html

使用示例代码:


# -*- coding:utf-8 -*-
 
import redis
from redis import WatchError
from concurrent.futures import ProcessPoolExecutor
 
r = redis.Redis(host='127.0.0.1', port=6379)
 
 
# 减库存函数, 循环直到减库存完成
# 库存充足, 减库存成功, 返回True
# 库存不足, 减库存失败, 返回False
 
def reduce_stock():
 
    # python中redis事务是通过pipeline的封装实现的
    with r.pipeline() as pipe:
        while True:
            try:
                # watch库存键, multi后如果该key被其他客户端改变, 事务操作会抛出WatchError异常
                pipe.watch('stock:count')
                count = int(pipe.get('stock:count'))
                if count > 0:  # 有库存
                    # 事务开始
                    pipe.multi()
                    pipe.decr('stock:count')
                    # 把命令推送过去
                    # execute返回命令执行结果列表, 这里只有一个decr返回当前值
                    print(pipe.execute()[0])
                    return True
                else:
                    return False
            except WatchError as ex:
                # 打印WatchError异常, 观察被watch锁住的情况
                print(ex)
                pipe.unwatch()
 
 
def worker():
    while True:
        # 没有库存就退出
        if not reduce_stock():
            break
 
 
if __name__ == "__main__":
    # 设置库存为100
    r.set("stock:count", 100)
 
    # 多进程模拟多个客户端提交
    with ProcessPoolExecutor() as pool:
        for _ in range(10):
            pool.submit(worker)

python 单线程 和 多线程
python单线程

# -*- coding:utf-8 -*-
from time import ctime, sleep
 
 
def music(argv):
    for i in range(2):
        print "listen music  %s. %s" % (argv, ctime())
        sleep(1)
 
 
def movie(argv):
    for i in range(2):
        print "watch movie!  %s. %s" % (argv, ctime())
        sleep(5)
 
if __name__ == '__main__':
    music(u'trouble is a friend')
    movie(u'变形金刚')
    print "all over %s" % ctime()

python多线程
Python中使用线程有两种方式:函数 或者用 类来包装线程对象。

函数式 :调用thread模块中的start_new_thread()函数来产生新线程。
语法如下: thread.start_new_thread(function, args[, kwargs])
参数说明:
function : 线程函数。
args : 传递给线程函数的参数,他必须是个tuple类型。
kwargs : 可选参数。

import thread
import time
 
def print_time(thread_name, delay):
        count = 0
        while count < 5:
                time.sleep(delay)
                count += 1
                print "%s: %s" % (thread_name, time.ctime(time.time()))
 
 
if __name__ == "__main__":
    try:
            thread.start_new_thread(print_time, ("Thread-1", 2))
            thread.start_new_thread(print_time, ("Thread-2", 4))
    except BaseException as e:
            print e
            print "Error: unable to start thread"
    while 1:
            pass
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:

# coding=utf-8
# !/usr/bin/python
import threading
import time
 
exitFlag = 0
 
class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
 
    def run(self):
        print "Starting " + self.name
        print_time(self.name, self.counter, 5)
        print "Exiting " + self.name
 
 
def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1
 
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
 
thread1.start()
thread2.start()
print "Exiting Main Thread"

python提供了两个模块来实现多线程thread 和threading 。 thread有一些缺点,在threading 得到了弥补,强烈建议直接使用threading。

# coding=utf-8
import threading
from time import ctime, sleep
 
 
def music(argv):
    for i in range(2):
        print "listen music  %s. %s" % (argv, ctime())
        sleep(1)
 
 
def movie(argv):
    for i in range(2):
        print "watch movie  %s! %s" % (argv, ctime())
        sleep(5)
 
 
threads = []
t1 = threading.Thread(target=music, args=(u'trouble is a friend',))
threads.append(t1)
t2 = threading.Thread(target=movie, args=(u'变形金刚',))
threads.append(t2)
 
if __name__ == '__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()
    print "all over %s" % ctime()

setDaemon(True) 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。
start()开始线程活动。

# 调整程序:
if __name__ == '__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()
    
    t.join()
    print "all over %s" %ctime()

对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
注意: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。

import threading
import time
 
def worker(num):
    time.sleep(1)
    print("The num is  %d" % num)
    print t.getName()
    return
 
for i in range(20):
    t = threading.Thread(target=worker, args=(i,), name="testThread")
    t.start()
Thread方法说明
t.start()       激活线程,   
t.getName()     获取线程的名称   
t.setName()     设置线程的名称   
t.name          获取或设置线程的名称   
t.is_alive()    判断线程是否为激活状态   
t.isAlive()     判断线程是否为激活状态   
t.setDaemon()   设置为后台线程或前台线程(默认:False);通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。
                如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;
                如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止   
t.isDaemon()    判断是否为守护线程   
t.ident         获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。   
t.join()        逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义   
t.run()         线程被cpu调度后自动执行线程对象的run方法

线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法。
对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。
如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。
为了避免这种情况,引入了锁的概念。锁有两种状态:锁定 和 未锁定。
每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,
也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

# coding=utf-8
# !/usr/bin/python
import threading
import time
 
 
class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
 
    def run(self):
        print "Starting " + self.name
        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时将一直阻塞直到获得锁定
        # 否则超时后将返回False
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # 释放锁
        threadLock.release()
 
 
def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1
 
 
threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
# 添加线程到线程列表中
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
for t in threads:
    t.join()
print "Exiting Main Thread"

线程优先级队列 (Queue)

Python的Queue模块中提供了同步的、线程安全的队列类。
包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

Queue模块中的常用方法:

Queue.qsize()    返回队列的大小
Queue.empty()    如果队列为空,返回True,反之False
Queue.full()     如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]]) 获取队列,timeout是等待时间
Queue.get_nowait()            相当Queue.get(False)
Queue.put(item)               写入队列,timeout是等待时间
Queue.put_nowait(item)        相当Queue.put(item, False)
Queue.task_done()             在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join()                  实际上意味着等到队列为空,再执行别的操作
# coding=utf-8
# !/usr/bin/python
import Queue
import threading
import time
 
exitFlag = 0
 
 
class myThread(threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
 
    def run(self):
        print "Starting " + self.name
        process_data(self.name, self.q)
        print "Exiting " + self.name
 
 
def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print "%s processing %s" % (threadName, data)
        else:
            queueLock.release()
        time.sleep(1)
 
 
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1
 
# 创建线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1
 
# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()
 
# 等待队列清空
while not workQueue.empty():
    pass
# 通知线程退出
exitFlag = 1
# 等待所有线程完成
for t in threads:
    t.join()
print "Exiting Main Thread"

线程锁 threading.RLock 和 threading.Lock

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。为了保证数据的准确性,引入了锁的概念。

所以,可能出现如下问题:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1, 那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。

import threading
import time
globals_num = 0
lock = threading.RLock()
 
 
def func():
    lock.acquire()  # 获得锁
    global globals_num
    globals_num += 1
    time.sleep(1)
    print(globals_num)
    lock.release()  # 释放锁
 
for i in range(10):
    t = threading.Thread(target=func)
    t.start()
    pass

threading.RLock 和 threading.Lock 的区别

RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

import threading  
lock = threading.Lock()    #Lock对象 
lock.acquire()  
lock.acquire()  #产生了死琐。 
lock.release() 
lock.release()  


import threading  
rLock = threading.RLock()  #RLock对象 
rLock.acquire()  
rLock.acquire()    #在同一线程内,程序不会堵塞。 
rLock.release() 
rLock.release()
threading.Event

python线程的事件用于主线程控制其他线程的执行。事件主要提供了三个方法 set、wait、clear。 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行event.wait方法时就会阻塞,如果“Flag”值为True,那么event.wait方法时便不再阻塞。
clear: 将“Flag”设置为False
set: 将“Flag”设置为True
Event.isSet() :判断标识位是否为Ture。

import threading
 
def do(event):
    print('start')
    event.wait()
    print('execute')
 
event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()
 
event_obj.clear()
# inp = input('input:')
inp = raw_input('input:')
if inp == 'true':
    event_obj.set()

当线程执行的时候,如果flag为False,则线程会阻塞,当flag为True的时候,线程不会阻塞。它提供了本地和远程的并发性。
threading.Condition
一个condition变量总是与某些类型的锁相联系,这个可以使用默认的情况或创建一个,
当几个condition变量必须共享和同一个锁的时候,是很有用的。锁是conditon对象的一部分:没有必要分别跟踪。

condition变量服从上下文管理协议:with语句块封闭之前可以获取与锁的联系。
acquire() 和 release() 会调用与锁相关联的相应的方法。
其他和锁关联的方法必须被调用,wait()方法会释放锁,
当另外一个线程使用 notify() or notify_all()唤醒它之前会一直阻塞。一旦被唤醒,wait()会重新获得锁并返回,

Condition类实现了一个conditon变量。这个conditiaon变量允许一个或多个线程等待,直到他们被另一个线程通知。
如果lock参数,被给定一个非空的值,,那么他必须是一个lock或者Rlock对象,它用来做底层锁。否则,会创建一个新的Rlock对象,用来做底层锁。

wait(timeout=None) :等待通知,或者等到设定的超时时间。
当调用这wait()方法时,如果调用它的线程没有得到锁,那么会抛出一个RuntimeError异常。
wati()释放锁以后,在被调用相同条件的另一个进程用notify() or notify_all() 叫醒之前会一直阻塞。
wait()还可以指定一个超时时间。 如果有等待的线程,notify()方法会唤醒一个在等待conditon变量的线程。notify_all() 则会唤醒所有在等待conditon变量的线程。

注意: notify()和notify_all()不会释放锁,也就是说,线程被唤醒后不会立刻返回他们的wait() 调用。
除非线程调用notify()和notify_all()之后放弃了锁的所有权。
在典型的设计风格里,利用condition变量用锁去通许访问一些共享状态,线程在获取到它想得到的状态前,会反复调用wait()。
修改状态的线程在他们状态改变时调用 notify() or notify_all(),用这种方式,线程会尽可能的获取到想要的一个等待者状态。

例子:生产者-消费者模型

import threading
import time
 
def consumer(cond):
    with cond:
        print("consumer before wait")
        cond.wait()
        print("consumer after wait")
 
def producer(cond):
    with cond:
        print("producer before notifyAll")
        cond.notifyAll()
        print("producer after notifyAll")
 
condition = threading.Condition()
c1 = threading.Thread(name="c1", target=consumer, args=(condition,))
c2 = threading.Thread(name="c2", target=consumer, args=(condition,))
p = threading.Thread(name="p", target=producer, args=(condition,))
 
c1.start()
time.sleep(2)
c2.start()
time.sleep(2)
p.start()

python 多进程共享变量
https://my.oschina.net/leejun2005/blog/203148

共享内存 (Shared memory)
Data can be stored in a shared memory map using Value or Array.

For example, the following code. https://docs.python.org/2/library/multiprocessing.html#sharing-state-between-processes

在使用并发设计的时候最好尽可能的避免共享数据,尤其是在使用多进程的时候。如果你真有需要要共享数据, multiprocessing提供了两种方式。

multiprocessing 中的 Array 和 Value。数据可以用 Value 或 Array 存储在一个共享内存地图里,如下:

from multiprocessing import Array, Value, Process
 
def func(a, b):
    a.value = 3.333333333333333
    for j in range(len(b)):
        b[j] = -b[j]
 
if __name__ == "__main__":
    num = Value('d', 0.0)
    arr = Array('i', range(11))
 
    if 0:
        t = Process(target=func, args=(num, arr))
        t.start()
        t.join()
    else:
        c = Process(target=func, args=(num, arr))
        d = Process(target=func, args=(num, arr))
        c.start()
        d.start()
        c.join()
        d.join()
 
    print(num.value)
    print(arr[:])
    for i in arr:
        print i,
输出
3.33333333333
0 1 2 3 4 5 6 7 8 9 10

创建 num 和 arr 时,“d”和“i”参数 由Array模块使用的typecodes创建:“d”表示一个双精度的浮点数,“i”表示一个有符号的整数,这些共享对象将被线程安全的处理。

Array(‘i’, range(10))中的‘i’参数:   
‘c’: ctypes.c_char    
‘u’: ctypes.c_wchar    
‘b’: ctypes.c_byte    
‘B’: ctypes.c_ubyte
‘h’: ctypes.c_short    
‘H’: ctypes.c_ushort   
‘i’: ctypes.c_int     
‘I’: ctypes.c_uint  
‘l’: ctypes.c_long,    
‘L’: ctypes.c_ulong    
‘f’: ctypes.c_float    
‘d’: ctypes.c_double

Server process
A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value and Array.

https://docs.python.org/2/library/multiprocessing.html#managers

multiprocessing 中的 Manager()

Python中进程间共享数据,除了基本的queue,pipe和value+array外,还提供了更高层次的封装。使用multiprocessing.Manager可以简单地使用这些高级接口。
Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。
Manager支持的类型有list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array。

from multiprocessing import Process, Manager
 
def f(d, l):
    d["name"] = "king"
    d["age"] = 100
    d["Job"] = "python"
    l.reverse()
 
if __name__ == "__main__":
    with Manager() as man:
        d_temp = man.dict()
        l_temp = man.list(range(10))
 
        p = Process(target=f, args=(d_temp, l_temp))
        p.start()
        p.join()
 
        print(d_temp)
        print(l_temp) 

Server process manager 比 shared memory 更灵活,因为它可以支持任意的对象类型。另外,一个单独的manager可以通过进程在网络上不同的计算机之间共享,不过他比shared memory要慢。

python 协程
关于协程,可以参考 greenlet,stackless,gevent,eventlet等的实现。

我们都知道并发(不是并行)编程目前有四种方式,多进程,多线程,异步,和协程。

多进程编程在python中有类似C的os.fork,当然还有更高层封装的multiprocessing标准库,在之前写过的python高可用程序设计方法http://www.cnblogs.com/hymenz/p/3488837.html中提供了类似nginx中master process和worker process间信号处理的方式,保证了业务进程的退出可以被主进程感知。

多线程编程python中有Thread和threading,在linux下所谓的线程,实际上是LWP轻量级进程,其在内核中具有和进程相同的调度方式,有关LWP,COW(写时拷贝),fork,vfork,clone等的资料较多,这里不再赘述。

异步在linux下主要有三种实现select,poll,epoll 。

说 python 的 协程 肯定要说yield

#coding=utf-8
import time
import sys
# 生产者
def produce(l):
    i=0
    while 1:
        if i < 5:
            l.append(i)
            yield i
            i=i+1
            time.sleep(1)
        else:
            return
      
# 消费者
def consume(l):
    p = produce(l)
    while 1:
        try:
            p.next()
            while len(l) > 0:
                print l.pop()
        except StopIteration:
            sys.exit(0)
l = []
consume(l)

在上面的例子中,当程序执行到produce的yield i时,返回了一个generator,当我们在custom中调用p.next(),程序又返回到produce的yield i继续执行,这样l中又append了元素,然后我们print l.pop(),直到p.next()引发了StopIteration异常。

通过上面的例子我们看到协程的调度对于内核来说是不可见的,协程间是协同调度的,这使得并发量在上万的时候,协程的性能是远高于线程的。

import stackless
import urllib2
def output():
    while 1:
        url=chan.receive()
        print url
        f=urllib2.urlopen(url)
        #print f.read()
        print stackless.getcurrent()
     
def input():
    f=open('url.txt')
    l=f.readlines()
    for i in l:
        chan.send(i)
chan=stackless.channel()
[stackless.tasklet(output)() for i in xrange(10)]
stackless.tasklet(input)()
stackless.run()

协程的好处:

无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

协程,又称微线程,纤程。英文名Coroutine。

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。

子程序调用总是一个入口,一次返回,调用顺序是明确的。

而协程的调用和子程序不同。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

看起来A、B的执行有点像多线程,
但协程的特点在于是一个线程执行,

那和多线程比,协程有何优势?
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,
因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制。
因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?
最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。
虽然支持不完全,但已经可以发挥相当大的威力了。
一个例子:

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

import time
 
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'
 
def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()
 
if __name__=='__main__':
    c = consumer()
    produce(c)

执行结果:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

        1. 首先调用c.next()启动生成器;
        2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
        3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
        4. produce拿到consumer处理的结果,继续生产下一条消息;
        5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:“子程序就是协程的一种特例”

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。
协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),
event loop是协程执行的控制点,如果你希望执行协程,就需要用到它们。
event loop提供了如下的特性:

注册、执行、取消延时调用(异步函数)、创建用于通信的client和server协议(工具)、创建和别的程序通信的子进程和协议(工具) 把函数调用送入线程池中

协程示例:


#---------python3_start---------------
import asyncio
async def cor1():
	print("COR1 start")
	await cor2()
	print("COR1 end")


async def cor2():
	print("COR2")

loop = asyncio.get_event_loop()
loop.run_until_complete(cor1())
loop.close()
#---------python3_end---------------

最后三行是重点。

        asyncio.get_event_loop() : asyncio启动默认的event loop 
        run_until_complete() :         这个函数是阻塞执行的,知道所有的异步函数执行完成, 
        close() :                                 关闭 event loop。

python 的 greenlet 模块

import greenlet
def fun1():
    print("12")
    gr2.switch()
    print("56")
    gr2.switch()
 
def fun2():
    print("34")
    gr1.switch()
    print("78")
 
gr1 = greenlet.greenlet(fun1)
gr2 = greenlet.greenlet(fun2)
gr1.switch()
gevent

gevent属于第三方模块需要下载安装包

pip3 install --upgrade pip3 
pip3 install gevent 
import gevent
def fun1():
    print("www.baidu.com")  # 第一步
    gevent.sleep(0)
    print("end the baidu.com")  # 第三步
 
def fun2():
    print("www.zhihu.com")  # 第二步
    gevent.sleep(0)
    print("end th zhihu.com")  # 第四步
 
gevent.joinall([
    gevent.spawn(fun1),
    gevent.spawn(fun2),
])

遇到IO操作自动切换:

import gevent
import requests
 
def func(url):
    print("get: %s" % url)
    gevent.sleep(0)
    proxies = {
        "http": "http://172.17.18.80:8080",
        "https": "http://172.17.18.80:8080",
    }
 
    date = requests.get(url, proxies=proxies)
    ret = date.text
    print(url, len(ret))
 
gevent.joinall([
    gevent.spawn(func, 'https://www.baidu.com/'),
    gevent.spawn(func, 'http://www.sina.com.cn/'),
    gevent.spawn(func, 'http://www.qq.com/'),
])

http://www.cnblogs.com/zingp/p/5911537.html

Python黑魔法 --- 异步IO( asyncio) 协程


http://www.cnblogs.com/gide/p/6187080.html
python中多进程+协程的使用以及为什么要用它: http://blog.csdn.net/lambert310/article/details/51162634
从两个简单例子窥视协程的惊人性能(Python):http://walkerqt.blog.51cto.com/1310630/1439034
greenlet:http://greenlet.readthedocs.org/en/latest/
eventlet: http://eventlet.net/
http://gashero.iteye.com/blog/442177

mutilprocess简介
像线程一样管理进程,这个是mutilprocess的核心,他与threading很是相像,对多核CPU的利用率会比threading好的多。

简单的创建进程

import multiprocessing
 
def worker(num):
    """thread worker function"""
    print 'Worker:', num
    return
 
if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

确定当前的进程,即是给进程命名,方便标识区分,跟踪

import multiprocessing
import time
 
def worker():
    name = multiprocessing.current_process().name
    print name, 'Starting'
    time.sleep(2)
    print name, 'Exiting'
 
def my_service():
    name = multiprocessing.current_process().name
    print name, 'Starting'
    time.sleep(3)
    print name, 'Exiting'
 
if __name__ == '__main__':
    service = multiprocessing.Process(name='my_service',
                                      target=my_service)
    worker_1 = multiprocessing.Process(name='worker 1',
                                       target=worker)
    worker_2 = multiprocessing.Process(target=worker) # default name
 
    worker_1.start()
    worker_2.start()
    service.start()

守护进程
守护进程就是不阻挡主程序退出,自己干自己的。 mutilprocess.setDaemon(True)就这句。

等待守护进程退出,要加上join,join可以传入浮点数值,等待n久就不等了

import multiprocessing
import time
import sys
 
def daemon():
    name = multiprocessing.current_process().name
    print 'Starting:', name
    time.sleep(2)
    print 'Exiting :', name
 
def non_daemon():
    name = multiprocessing.current_process().name
    print 'Starting:', name
    print 'Exiting :', name
 
if __name__ == '__main__':
    d = multiprocessing.Process(name='daemon',
                                target=daemon)
    d.daemon = True
 
    n = multiprocessing.Process(name='non-daemon',
                                target=non_daemon)
    n.daemon = False
 
    d.start()
    n.start()
 
    d.join(1)
    print 'd.is_alive()', d.is_alive()
    n.join()

终止进程
最好使用 poison pill,强制的使用terminate()。注意 terminate之后要join,使其可以更新状态

import multiprocessing
import time
 
def slow_worker():
    print 'Starting worker'
    time.sleep(0.1)
    print 'Finished worker'
 
if __name__ == '__main__':
    p = multiprocessing.Process(target=slow_worker)
    print 'BEFORE:', p, p.is_alive()
 
    p.start()
    print 'DURING:', p, p.is_alive()
 
    p.terminate()
    print 'TERMINATED:', p, p.is_alive()
 
    p.join()
    print 'JOINED:', p, p.is_alive()

进程的退出状态

 == 0     未生成任何错误
 0           进程有一个错误,并以该错误码退出
 < 0       进程由一个-1 * exitcode信号结束
import multiprocessing
import sys
import time
 
def exit_error():
    sys.exit(1)
 
def exit_ok():
    return
 
def return_value():
    return 1
 
def raises():
    raise RuntimeError('There was an error!')
 
def terminated():
    time.sleep(3)
 
if __name__ == '__main__':
    jobs = []
    for f in [exit_error, exit_ok, return_value, raises, terminated]:
        print 'Starting process for', f.func_name
        j = multiprocessing.Process(target=f, name=f.func_name)
        jobs.append(j)
        j.start()
 
    jobs[-1].terminate()
 
    for j in jobs:
        j.join()
        print '%15s.exitcode = %s' % (j.name, j.exitcode)

日志
方便的调试,可以用logging

import multiprocessing
import logging
import sys
 
def worker():
    print 'Doing some work'
    sys.stdout.flush()
 
if __name__ == '__main__':
    multiprocessing.log_to_stderr()
    logger = multiprocessing.get_logger()
    logger.setLevel(logging.INFO)
    p = multiprocessing.Process(target=worker)
    p.start()
    p.join()

派生进程
利用class来创建进程,定制子类

import multiprocessing
 
class Worker(multiprocessing.Process):
 
    def run(self):
        print 'In %s' % self.name
        return
 
if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = Worker()
        jobs.append(p)
        p.start()
    for j in jobs:
        j.join()

python进程间传递消息
这一块我之前结合SocketServer写过一点,见Python多进程

一般的情况是Queue来传递。

import multiprocessing
 
class MyFancyClass(object):
 
    def __init__(self, name):
        self.name = name
 
    def do_something(self):
        proc_name = multiprocessing.current_process().name
        print 'Doing something fancy in %s for %s!' % \
            (proc_name, self.name)
 
def worker(q):
    obj = q.get()
    obj.do_something()
 
if __name__ == '__main__':
    queue = multiprocessing.Queue()
 
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
 
    queue.put(MyFancyClass('Fancy Dan'))
 
    # Wait for the worker to finish
    queue.close()
    queue.join_thread()
    p.join()
import multiprocessing
import time
 
class Consumer(multiprocessing.Process):
 
    def __init__(self, task_queue, result_queue):
        multiprocessing.Process.__init__(self)
        self.task_queue = task_queue
        self.result_queue = result_queue
 
    def run(self):
        proc_name = self.name
        while True:
            next_task = self.task_queue.get()
            if next_task is None:
                # Poison pill means shutdown
                print '%s: Exiting' % proc_name
                self.task_queue.task_done()
                break
            print '%s: %s' % (proc_name, next_task)
            answer = next_task()
            self.task_queue.task_done()
            self.result_queue.put(answer)
        return
 
class Task(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __call__(self):
        time.sleep(0.1) # pretend to take some time to do the work
        return '%s * %s = %s' % (self.a, self.b, self.a * self.b)
    def __str__(self):
        return '%s * %s' % (self.a, self.b)
 
if __name__ == '__main__':
    # Establish communication queues
    tasks = multiprocessing.JoinableQueue()
    results = multiprocessing.Queue()
 
    # Start consumers
    num_consumers = multiprocessing.cpu_count() * 2
    print 'Creating %d consumers' % num_consumers
    consumers = [ Consumer(tasks, results)
                  for i in xrange(num_consumers) ]
    for w in consumers:
        w.start()
 
    # Enqueue jobs
    num_jobs = 10
    for i in xrange(num_jobs):
        tasks.put(Task(i, i))
 
    # Add a poison pill for each consumer
    for i in xrange(num_consumers):
        tasks.put(None)
 
    # Wait for all of the tasks to finish
    tasks.join()
 
    # Start printing results
    while num_jobs:
        result = results.get()
        print 'Result:', result
        num_jobs -= 1

进程间信号传递
Event提供一种简单的方法,可以在进程间传递状态信息。事件可以切换设置和未设置状态。通过使用一个可选的超时值,时间对象的用户可以等待其状态从未设置变为设置。

import multiprocessing
import time
 
def wait_for_event(e):
    """Wait for the event to be set before doing anything"""
    print 'wait_for_event: starting'
    e.wait()
    print 'wait_for_event: e.is_set()->', e.is_set()
 
def wait_for_event_timeout(e, t):
    """Wait t seconds and then timeout"""
    print 'wait_for_event_timeout: starting'
    e.wait(t)
    print 'wait_for_event_timeout: e.is_set()->', e.is_set()
 
if __name__ == '__main__':
    e = multiprocessing.Event()
    w1 = multiprocessing.Process(name='block', 
                                 target=wait_for_event,
                                 args=(e,))
    w1.start()
 
    w2 = multiprocessing.Process(name='nonblock', 
                                 target=wait_for_event_timeout, 
                                 args=(e, 2))
    w2.start()
 
    print 'main: waiting before calling Event.set()'
    time.sleep(3)
    e.set()
    print 'main: event is set'

由于Python设计的限制(我说的是咱们常用的CPython)。最多只能用满1个CPU核心。
Python提供了非常好用的多进程包multiprocessing,你只需要定义一个函数,Python会替你完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。

1、新建单一进程

如果我们新建少量进程,可以如下:

import multiprocessing
import time
def func(msg):
  for i in xrange(3):
    print msg
    time.sleep(1)
if __name__ == "__main__":
  p = multiprocessing.Process(target=func, args=("hello", ))
  p.start()
  p.join()
  print "Sub-process done."

2、使用进程池(非阻塞)

是的,你没有看错,不是线程池。它可以让你跑满多核CPU,而且使用方法非常简单。

注意要用apply_async,如果落下async,就变成阻塞版本了。

processes=4是最多并发进程数量。

import multiprocessing
import time
def func(msg):
  for i in xrange(3):
    print msg
    time.sleep(1)
if __name__ == "__main__":
  pool = multiprocessing.Pool(processes=4)
  for i in xrange(10):
    msg = "hello %d" %(i)
    pool.apply_async(func, (msg, ))
  pool.close()
  pool.join()
  print "Sub-process(es) done."

函数解释:

apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的
close()    关闭pool,使其不在接受新的任务。
terminate()    结束工作进程,不在处理未完成的任务。
join()    主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

使用进程池(阻塞)

#coding: utf-8
import multiprocessing
import time
 
def func(msg):
    print "msg:", msg
    time.sleep(3)
    print "end"
 
if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
 
    print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"
    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print "Sub-process(es) done."

3、使用Pool,并需要关注结果

更多的时候,我们不仅需要多进程执行,还需要关注每个进程的执行结果,如下:

import multiprocessing
import time
def func(msg):
  for i in xrange(3):
    print msg
    time.sleep(1)
  return "done " + msg
if __name__ == "__main__":
  pool = multiprocessing.Pool(processes=4)
  result = []
  for i in xrange(10):
    msg = "hello %d" %(i)
    result.append(pool.apply_async(func, (msg, )))
  pool.close()
  pool.join()
  for res in result:
    print res.get()
  print "Sub-process(es) done."

示例

import multiprocessing
 
def do_calculation(data):
    return data*2
 
def start_process():
    print 'Starting', multiprocessing.current_process().name
 
if __name__ == '__main__':
    inputs = list(range(10))
    print 'Inputs  :', inputs
 
    builtin_output = map(do_calculation, inputs)
    print 'Build-In :', builtin_output
 
    pool_size = multiprocessing.cpu_count()*2
    pool = multiprocessing.Pool(processes=pool_size, initializer=start_process,)
    # 默认情况下,Pool会创建固定数目的工作进程,并向这些工作进程传递作业,直到再没有更多作业为止。
    # maxtasksperchild参数为每个进程执行task的最大数目,
    # 设置maxtasksperchild参数可以告诉池在完成一定数量任务之后重新启动一个工作进程,
    # 来避免运行时间很长的工作进程消耗太多的系统资源。
    # pool = multiprocessing.Pool(processes=pool_size, initializer=start_process, maxtasksperchild=2)
    print '-' * 20
    pool_outputs = pool.map(do_calculation, inputs)
    pool.close()
    pool.join()
 
    print 'Pool  :', pool_outputs

使用多个进程池

#coding: utf-8
import multiprocessing
import os, time, random
 
def Lee():
    print "\nRun task Lee-%s" %(os.getpid()) #os.getpid()获取当前的进程的ID
    start = time.time()
    time.sleep(random.random() * 10) #random.random()随机生成0-1之间的小数
    end = time.time()
    print 'Task Lee, runs %0.2f seconds.' %(end - start)
 
def Marlon():
    print "\nRun task Marlon-%s" %(os.getpid())
    start = time.time()
    time.sleep(random.random() * 40)
    end=time.time()
    print 'Task Marlon runs %0.2f seconds.' %(end - start)
 
def Allen():
    print "\nRun task Allen-%s" %(os.getpid())
    start = time.time()
    time.sleep(random.random() * 30)
    end = time.time()
    print 'Task Allen runs %0.2f seconds.' %(end - start)
 
def Frank():
    print "\nRun task Frank-%s" %(os.getpid())
    start = time.time()
    time.sleep(random.random() * 20)
    end = time.time()
    print 'Task Frank runs %0.2f seconds.' %(end - start)
        
if __name__=='__main__':
    function_list=  [Lee, Marlon, Allen, Frank] 
    print "parent process %s" %(os.getpid())
 
    pool=multiprocessing.Pool(4)
    for func in function_list:
        pool.apply_async(func)     #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中
 
    print 'Waiting for all subprocesses done...'
    pool.close()
    pool.join()    #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
    print 'All subprocesses done.'

multiprocessing pool map

#coding: utf-8
import multiprocessing 
 
def m1(x): 
    print x * x 
 
if __name__ == '__main__': 
    pool = multiprocessing.Pool(multiprocessing.cpu_count()) 
    i_list = range(8)
    pool.map(m1, i_list)
#coding: utf-8
import multiprocessing
import logging
 
def create_logger(i):
    print i
 
class CreateLogger(object):
    def __init__(self, func):
        self.func = func
 
if __name__ == '__main__':
    ilist = range(10)
 
    cl = CreateLogger(create_logger)
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    pool.map(cl.func, ilist)
 
    print "hello------------>"

Python 多进程 multiprocessing.Pool类详解
multiprocessing模块

multiprocessing包是Python中的多进程管理包。它与 threading.Thread类似,可以利用multiprocessing.Process对象来创建一个进程。该进程可以允许放在Python程序内部编写的函数中。该Process对象与Thread对象的用法相同,拥有is_alive()、join([timeout])、run()、start()、terminate()等方法。属性有:authkey、daemon(要通过start()设置)、exitcode(进程在运行时为None、如果为–N,表示被信号N结束)、name、pid。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类,用来同步进程,其用法也与threading包中的同名类一样。multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

这个模块表示像线程一样管理进程,这个是multiprocessing的核心,它与threading很相似,对多核CPU的利用率会比threading好的多。

看一下Process类的构造方法:

__init__(self, group=None, target=None, name=None, args=(), kwargs={})
参数说明: 
group:进程所属组。基本不用 
target:表示调用对象。 
args:表示调用对象的位置参数元组。 
name:别名 
kwargs:表示调用对象的字典。

创建进程的简单实例:

#coding=utf-8
import multiprocessing
 
def do(n) :
  #获取当前线程的名字
  name = multiprocessing.current_process().name
  print name,'starting'
  print "worker ", n
  return 
 
if __name__ == '__main__' :
  numList = []
  for i in xrange(5) :
    p = multiprocessing.Process(target=do, args=(i,))
    numList.append(p)
    p.start()
    p.join()
    print "Process end."

执行结果:

Process-1 starting
worker  0
Process end.
Process-2 starting
worker  1
Process end.
Process-3 starting
worker  2
Process end.
Process-4 starting
worker  3
Process end.
Process-5 starting
worker  4
Process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,并用其start()方法启动,这样创建进程比fork()还要简单。
join()方法表示等待子进程结束以后再继续往下运行,通常用于进程间的同步。

注意:
在Windows上要想使用进程模块,就必须把有关进程的代码写在当前.py文件的if __name__ == ‘__main__’ :语句的下面,才能正常使用Windows下的进程模块。Unix/Linux下则不需要。

Pool类
在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。

Pool类描述了一个工作进程池,他有几种不同的方法让任务卸载工作进程。 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。我们可以用Pool类创建一个进程池,展开提交的任务给进程池。

一个进程池对象可以控制工作进程池的哪些工作可以被提交,它支持超时和回调的异步结果,有一个类似map的实现。

processes :使用的工作进程的数量,如果processes是None那么使用os.cpu_count()返回的数量。
initializer: 如果initializer是None,那么每一个工作进程在开始的时候会调用initializer(*initargs)。 
maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个心的工作进程来替代原进程,来让闲置的资源被释放。
maxtasksperchild默认是None,意味着只要Pool存在工作进程就会一直存活。 
context:  用在制定工作进程启动时的上下文,一般使用multiprocessing.Pool() 或者一个context对象的Pool()方法来创建一个池,两种方法都适当的设置了context

注意:Pool对象的方法只可以被创建pool的进程所调用。

下面介绍一下multiprocessing 模块下的Pool类下的几个方法

进程池的方法

apply(func[, args[, kwds]]) :使用arg和kwds参数调用func函数,结果返回前会一直阻塞,
                              由于这个原因,apply_async()更适合并发执行,另外,func函数仅被pool中的一个进程运行。   

apply_async(func[, args[, kwds[, callback[, error_callback]]]]) : 
        apply()方法的一个变体,会返回一个结果对象。
        如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调时会调用callback,
	调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。   
close() :    阻止更多的任务提交到pool,待任务完成后,工作进程会退出。   
terminate() :不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。
join() :      wait工作线程的退出,在调用join()前,必须调用close() or terminate()。
              这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。   
map(func, iterable[, chunksize])   
map_async(func, iterable[, chunksize[, callback[, error_callback]]])
imap(func, iterable[, chunksize])
imap_unordered(func, iterable[, chunksize])   
starmap(func, iterable[, chunksize])
starmap_async(func, iterable[, chunksize[, callback[, error_back]]])

apply()函数原型:

apply(func[, args=()[, kwds={}]])

该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。

apply 方法 示例

# apply
from multiprocessing import Pool
import time
 
def f1(arg):
    time.sleep(0.5)
    print(arg)
    return arg + 100
 
if __name__ == "__main__":
    pool = Pool(5)
    for i in range(1, 10):
        pool.apply(func=f1, args=(i,))
   

apply_async()函数原型:

apply_async(func[, args=()[, kwds={}[, callback=None]]])

与apply用法一样,但它是非阻塞且支持结果返回进行回调。

apply_async 方法 示例

# apply_async
from multiprocessing import Pool
 
def f1(i):
    time.sleep(1)
    print(i)
    return i + 100
 
def f2(arg):
    print(arg)
 
if __name__ == "__main__":
    pool = Pool(5)
    for i in range(1, 10):
        pool.apply_async(func=f1, args=(i,), callback=f2)
    pool.close()
    pool.join()

map()函数原型:

map(func, iterable[, chunksize=None])

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。

close()
关闭进程池(pool),使其不在接受新的任务。
terminate()
结束工作进程,不在处理未处理的任务。
join()
主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。

multiprocessing.Pool类的实例:

import time
from multiprocessing import Pool
def run(fn):
  #fn: 函数参数是数据列表的一个元素
  time.sleep(1)
  return fn*fn

if __name__ == "__main__":
  testFL = [1,2,3,4,5,6]  
  print 'shunxu:' #顺序执行(也就是串行执行,单进程)
  s = time.time()
  for fn in testFL:
    run(fn)

  e1 = time.time()
  print "顺序执行时间:", int(e1 - s)

  print 'concurrent:' #创建多个进程,并行执行
  pool = Pool(5)  #创建拥有5个进程数量的进程池
  #testFL:要处理的数据列表,run:处理testFL列表中数据的函数
  rl =pool.map(run, testFL) 
  pool.close()#关闭进程池,不再接受新的进程
  pool.join()#主进程阻塞等待子进程的退出
  e2 = time.time()
  print "并行执行时间:", int(e2-e1)
  print rl

执行结果:

shunxu:
顺序执行时间: 6
concurrent:
并行执行时间: 2
[1, 4, 9, 16, 25, 36]

上例是一个创建多个进程并发处理与顺序执行处理同一数据,所用时间的差别。从结果可以看出,并发执行的时间明显比顺序执行要快很多,但是进程是要耗资源的,所以平时工作中,进程数也不能开太大。
程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。

再看一个实例:

import time
from multiprocessing import Pool
def run(fn) :
  time.sleep(2)
  print fn
if __name__ == "__main__" :
  startTime = time.time()
  testFL = [1,2,3,4,5]
  pool = Pool(10)#可以同时跑10个进程
  pool.map(run,testFL)
  pool.close()
  pool.join()   
  endTime = time.time()
  print "time :", endTime - startTime

执行结果:

21

3
4
5
time : 2.51999998093
再次执行结果如下:
1
34

2
5
time : 2.48600006104

结果中为什么还有空行和没有折行的数据呢?其实这跟进程调度有关,当有多个进程并行执行时,每个进程得到的时间片时间不一样,哪个进程接受哪个请求以及执行完成时间都是不定的,所以会出现输出乱序的情况。那为什么又会有没这行和空行的情况呢?因为有可能在执行第一个进程时,刚要打印换行符时,切换到另一个进程,这样就极有可能两个数字打印到同一行,并且再次切换回第一个进程时会打印一个换行符,所以就会出现空行的情况。

进程实战实例
并行处理某个目录下文件中的字符个数和行数,存入res.txt文件中,
每个文件一行,格式为:filename:lineNumber,charNumber

import os
import time
from multiprocessing import Pool
 
def getFile(path) :
  #获取目录下的文件list
  fileList = []
  for root, dirs, files in list(os.walk(path)) :
    for i in files :
      if i.endswith('.txt') or i.endswith('.10w') :
        fileList.append(root + "\\" + i)
  return fileList
 
def operFile(filePath) :
  #统计每个文件中行数和字符数,并返回
  filePath = filePath
  fp = open(filePath)
  content = fp.readlines()
  fp.close()
  lines = len(content)
  alphaNum = 0
  for i in content :
    alphaNum += len(i.strip('\n'))
  return lines,alphaNum,filePath
 
def out(list1, writeFilePath) :
  #将统计结果写入结果文件中
  fileLines = 0
  charNum = 0
  fp = open(writeFilePath,'a')
  for i in list1 :
    fp.write(i[2] + " 行数:"+ str(i[0]) + " 字符数:"+str(i[1]) + "\n")
    fileLines += i[0]
    charNum += i[1]
  fp.close()
  print fileLines, charNum
 
if __name__ == "__main__":
  #创建多个进程去统计目录中所有文件的行数和字符数
  startTime = time.time()
  filePath = "C:\\wcx\\a"
  fileList = getFile(filePath)
  pool = Pool(5)  
  resultList =pool.map(operFile, fileList)  
  pool.close()
  pool.join()
 
  writeFilePath = "c:\\wcx\\res.txt"
  print resultList
  out(resultList, writeFilePath)
  endTime = time.time()
  print "used time is ", endTime - startTime

执行结果:

1
耗时不到1秒,可见多进程并发执行速度是很快的。

我们已经见过了使用subprocess包来创建子进程,但这个包有两个很大的局限性:

1) 我们总是让subprocess运行外部的程序,而不是运行一个Python脚本内部编写的函数。

2) 进程间只通过管道进行文本交流。以上限制了我们将subprocess包应用到更广泛的多进程任务。

   (这样的比较实际是不公平的,因为subprocessing本身就是设计成为一个shell,而不是一个多进程管理包)

threading和multiprocessing
(请尽量先阅读Python多线程与同步)

multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

但在使用这些共享API的时候,我们要注意以下几点:

在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。
multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。
多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。
Process.PID中保存有PID,如果进程还没有start(),则PID为None。

我们可以从下面的程序中看到Thread对象和Process对象在使用上的相似性与结果上的不同。各个线程和进程都做一件事:打印PID。但问题是,所有的任务在打印的时候都会向同一个标准输出(stdout)输出。这样输出的字符会混合在一起,无法阅读。使用Lock同步,在一个任务输出完成之后,再允许另一个任务输出,可以避免多个任务同时向终端输出。

# Similarity and difference of multi thread vs. multi process
# Written by Vamei
 
import os
import threading
import multiprocessing
 
# worker function
def worker(sign, lock):
    lock.acquire()
    print(sign, os.getpid())
    lock.release()
 
# Main
print('Main:',os.getpid())
 
# Multi-thread
record = []
lock  = threading.Lock()
for i in range(5):
    thread = threading.Thread(target=worker,args=('thread',lock))
    thread.start()
    record.append(thread)
 
for thread in record:
    thread.join()
 
# Multi-process
record = []
lock = multiprocessing.Lock()
for i in range(5):
    process = multiprocessing.Process(target=worker,args=('process',lock))
    process.start()
    record.append(process)
 
for process in record:
    process.join()

所有Thread的PID都与主程序相同,而每个Process都有一个不同的PID。

(练习: 使用mutiprocessing包将Python多线程与同步中的多线程程序更改为多进程程序)

Pipe和Queue
正如我们在Linux多线程中介绍的管道PIPE和消息队列message queue,multiprocessing包中有Pipe类和Queue类来分别支持这两种IPC机制。Pipe和Queue可以用来传送常见的对象。

1) Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

下面的程序展示了Pipe的使用:

# Multiprocessing with Pipe
# Written by Vamei
 
import multiprocessing as mul
 
def proc1(pipe):
    pipe.send('hello')
    print('proc1 rec:',pipe.recv())
 
def proc2(pipe):
    print('proc2 rec:',pipe.recv())
    pipe.send('hello, too')
 
# Build a pipe
pipe = mul.Pipe()
 
# Pass an end of the pipe to process 1
p1   = mul.Process(target=proc1, args=(pipe[0],))
# Pass the other end of the pipe to process 2
p2   = mul.Process(target=proc2, args=(pipe[1],))
p1.start()
p2.start()
p1.join()
p2.join()

这里的Pipe是双向的。

Pipe对象建立的时候,返回一个含有两个元素的表,每个元素代表Pipe的一端(Connection对象)。我们对Pipe的某一端调用send()方法来传送对象,在另一端使用recv()来接收。

2) Queue与Pipe相类似,都是先进先出的结构。但Queue允许多个进程放入,多个进程从队列取出对象。Queue使用mutiprocessing.Queue(maxsize)创建,maxsize表示队列中可以存放对象的最大数量。

下面的程序展示了Queue的使用:

# Written by Vamei
import os
import multiprocessing
import time
#==================
# input worker
def inputQ(queue):
    info = str(os.getpid()) + '(put):' + str(time.time())
    queue.put(info)
 
# output worker
def outputQ(queue,lock):
    info = queue.get()
    lock.acquire()
    print (str(os.getpid()) + '(get):' + info)
    lock.release()
#===================
# Main
record1 = []   # store input processes
record2 = []   # store output processes
lock  = multiprocessing.Lock()    # To prevent messy print
queue = multiprocessing.Queue(3)
 
# input processes
for i in range(10):
    process = multiprocessing.Process(target=inputQ,args=(queue,))
    process.start()
    record1.append(process)
 
# output processes
for i in range(10):
    process = multiprocessing.Process(target=outputQ,args=(queue,lock))
    process.start()
    record2.append(process)
 
for p in record1:
    p.join()
 
queue.close()  # No more object will come, close the queue
 
for p in record2:
    p.join()

一些进程使用put()在Queue中放入字符串,这个字符串中包含PID和时间。另一些进程从Queue中取出,并打印自己的PID以及get()的字符串
进程池
进程池 (Process Pool)可以创建多个进程。这些进程就像是随时待命的士兵,准备执行任务(程序)。一个进程池中可以容纳多个待命的士兵。

比如下面的程序:

import multiprocessing as mul
 
def f(x):
    return x**2
 
pool = mul.Pool(5)
rel  = pool.map(f,[1,2,3,4,5,6,7,8,9,10])
print(rel)

我们创建了一个容许5个进程的进程池 (Process Pool) 。Pool运行的每个进程都执行f()函数。我们利用map()方法,将f()函数作用到表的每个元素上。这与built-in的map()函数类似,只是这里用5个进程并行处理。如果进程运行结束后,还有需要处理的元素,那么的进程会被用于重新运行f()函数。除了map()方法外,Pool还有下面的常用方法。

apply_async(func,args) 从进程池中取出一个进程执行func,args为func的参数。它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。

close() 进程池不再创建新的进程

join() wait进程池中的全部进程。必须对Pool先调用close()方法才能join。

练习

有下面一个文件download.txt。

www.sina.com.cn
www.163.com
www.iciba.com
www.cnblogs.com
www.qq.com
www.douban.com
使用包含3个进程的进程池下载文件中网站的首页。(你可以使用subprocess调用wget或者curl等下载工具执行具体的下载任务)

共享资源
我们在Python多进程初步已经提到,我们应该尽量避免多进程共享资源。多进程共享资源必然会带来进程间相互竞争。而这种竞争又会造成race condition,我们的结果有可能被竞争的不确定性所影响。但如果需要,我们依然可以通过共享内存和Manager对象这么做。

共享内存

在Linux进程间通信中,我们已经讲述了共享内存(shared memory)的原理,这里给出用Python实现的例子:

# modified from official documentation
import multiprocessing
 
def f(n, a):
    n.value   = 3.14
    a[0]      = 5
 
num   = multiprocessing.Value('d', 0.0)
arr   = multiprocessing.Array('i', range(10))
 
p = multiprocessing.Process(target=f, args=(num, arr))
p.start()
p.join()
 
print num.value
print arr[:]

这里我们实际上只有主进程和Process对象代表的进程。我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为0.0。而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享。

Manager

Manager对象类似于服务器与客户之间的通信 (server-client),与我们在Internet上的活动很类似。我们用一个进程作为服务器,建立Manager来真正存放资源。其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型。
import multiprocessing

def f(x, arr, l):
    x.value = 3.14
    arr[0] = 5
    l.append('Hello')
 
server = multiprocessing.Manager()
x    = server.Value('d', 0.0)
arr  = server.Array('i', range(10))
l    = server.list()
 
proc = multiprocessing.Process(target=f, args=(x, arr, l))
proc.start()
proc.join()
 
print(x.value)
print(arr)
print(l)

Manager利用list()方法提供了表的共享方式。实际上你可以利用dict()来共享词典,Lock()来共享threading.Lock(注意,我们共享的是threading.Lock,而不是进程的mutiprocessing.Lock。后者本身已经实现了进程共享)等。 这样Manager就允许我们共享更多样的对象。

文章来源:https://blog.csdn.net/freeking101/article/details/52511837

布施恩德可便相知重

微信扫一扫打赏

支付宝扫一扫打赏

×
标签:

给我留言