python基础:multiprocessing的使用

不同于C++或Java的多线程,python中是使用多进程来解决多项任务并发以提高效率的问题,依靠的是充分使用多核CPU的资源。这里是介绍mulitiprocessing的官方文档:https://docs.python.org/2/library/multiprocessing.html

一、多进程并发效果演示

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2. import time  
  3.   
  4. def worker_1(ts):  
  5.     print "run worker_1"  
  6.     time.sleep(ts)  
  7.     print "end worker_1"  
  8.   
  9. def worker_2(ts):  
  10.     print "run worker_2"  
  11.     time.sleep(ts)  
  12.     print "end worker_2"  
  13.   
  14. def worker_3(ts):  
  15.     print "run worker_3"  
  16.     time.sleep(ts)  
  17.     print "end worker_3"  
  18.   
  19. def worker_4(ts):  
  20.     print 'run worker_4'  
  21.     time.sleep(ts)  
  22.     print 'end worker_4'  
  23.   
  24. def worker_5(ts):  
  25.     print 'run worker_5'  
  26.     time.sleep(ts)  
  27.     print 'end worker_5'  
  28.   
  29. if __name__ == "__main__":  
  30.     proc1 = multiprocessing.Process(target = worker_1, args = (1,))  
  31.     proc2 = multiprocessing.Process(target = worker_2, args = (2,))  
  32.     proc3 = multiprocessing.Process(target = worker_3, args = (3,))  
  33.     proc4 = multiprocessing.Process(target = worker_4, args = (3,))  
  34.     proc5 = multiprocessing.Process(target = worker_5, args = (3,))  
  35.   
  36.     proc1.start()  
  37.     proc2.start()  
  38.     proc3.start()  
  39.     proc4.start()  
  40.     proc5.start()  
  41.   
  42.     print("The number of CPU is:" + str(multiprocessing.cpu_count()))  
  43.     for p in multiprocessing.active_children():  
  44.         print("child   p.name:" + p.name + "\tp.id" + str(p.pid))  
  45.     print "main_process finished."</span>  

运行结果:


分析:

通过上面的运行结果可以看到

(1)在主进程中start启动的5个进程彼此之间以及和主进程均存在并发关系,像上面worker_2在主进程的输出前输出,而且worker1、4、3、5分别无序输出‘run’就是并发最好的说明

(2)由于worker_1和worker_2分别sleep1秒和2秒,所以在主进程结束后依次结束,而worker_3、worker_4、worker_5都是sleep相同的3秒,最后它们三个进程无序输出(end4、end3、end5)更好的演示了并发效果

二、将进程写成class的范例

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2. import time  
  3.   
  4. class CounterProcess(multiprocessing.Process):  
  5.     def __init__(self, ts, arr):  
  6.         multiprocessing.Process.__init__(self)  
  7.         self.ts = ts  
  8.         self.arr = arr  
  9.   
  10.     def run(self):  
  11.         time.sleep(self.ts)  
  12.         sum = 0  
  13.         for i in self.arr:  
  14.             sum += i  
  15.         print 'sum = ' + str(sum)  
  16.   
  17.         c_time_cur_loc = time.localtime()  
  18.         counter_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  19.             c_time_cur_loc.tm_year, \  
  20.             c_time_cur_loc.tm_mon, \  
  21.             c_time_cur_loc.tm_mday, \  
  22.             c_time_cur_loc.tm_hour, \  
  23.             c_time_cur_loc.tm_min, \  
  24.             c_time_cur_loc.tm_sec \  
  25.             )  
  26.         print 'counter_process finished at ' + str(counter_timestamp)  
  27.   
  28. if __name__ == '__main__':  
  29.     arr = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]  
  30.     ts = 2  
  31.     counter = CounterProcess(ts, arr)  
  32.     counter.start()  
  33.   
  34.     for i in arr:  
  35.         print 'arr.member = ' + str(i)  
  36.   
  37.     m_time_cur_loc = time.localtime()  
  38.     main_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  39.         m_time_cur_loc.tm_year, \  
  40.         m_time_cur_loc.tm_mon, \  
  41.         m_time_cur_loc.tm_mday, \  
  42.         m_time_cur_loc.tm_hour, \  
  43.         m_time_cur_loc.tm_min, \  
  44.         m_time_cur_loc.tm_sec \  
  45.         )  
  46.     print 'main_process finished at ' + str(main_timestamp)</span>  

运行结果:


分析:

这个范例是在主进程中一次输出数组中的斐波那契数列,然后由一个进程counter去计算该数列的累加和。

其中在进程初始化的时候设置了让该进程sleep两秒,然后在输出的结果中我们也可以看到主进程首先结束,然后在两秒后counter进程完成累加和的运算并且结束(累加和应该不到1ms,直接可以忽略,所以两个进程结束的时间差恰好就是我们预设的2秒)

三、daemon和join

(1)daemon:daemon的作用是控制主线程与其他线程的关系,默认情况下daemon=False,也就是当主进程关闭后,在主进程中start出来的进程会继续正常运行,而如果手动设置daemon=True,那么在主进程结束后,从主进程中start的所有其他进程进程也会立刻随着主进程的结束而结束。

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2. import time  
  3.   
  4. class CounterProcess(multiprocessing.Process):  
  5.     def __init__(self, ts, arr):  
  6.         multiprocessing.Process.__init__(self)  
  7.         self.ts = ts  
  8.         self.arr = arr  
  9.   
  10.     def run(self):  
  11.         time.sleep(self.ts)  
  12.         sum = 0  
  13.         for i in self.arr:  
  14.             sum += i  
  15.         print 'sum = ' + str(sum)  
  16.   
  17.         c_time_cur_loc = time.localtime(time.time())  
  18.         counter_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  19.             c_time_cur_loc.tm_year, \  
  20.             c_time_cur_loc.tm_mon, \  
  21.             c_time_cur_loc.tm_mday, \  
  22.             c_time_cur_loc.tm_hour, \  
  23.             c_time_cur_loc.tm_min, \  
  24.             c_time_cur_loc.tm_sec \  
  25.             )  
  26.         print 'counter_process finished at ' + str(counter_timestamp)  
  27.   
  28. if __name__ == '__main__':  
  29.     arr = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]  
  30.     ts = 2  
  31.     counter = CounterProcess(ts, arr)  
  32.     counter.daemon = True  
  33.     counter.start()  
  34.     #counter.join()  
  35.   
  36.   
  37.     for i in arr:  
  38.         print 'arr.member = ' + str(i)  
  39.   
  40.     m_time_cur_loc = time.localtime(time.time())  
  41.     main_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  42.         m_time_cur_loc.tm_year, \  
  43.         m_time_cur_loc.tm_mon, \  
  44.         m_time_cur_loc.tm_mday, \  
  45.         m_time_cur_loc.tm_hour, \  
  46.         m_time_cur_loc.tm_min, \  
  47.         m_time_cur_loc.tm_sec \  
  48.         )  
  49.     print 'main_process finished at ' + str(main_timestamp)</span>  

运行结果:

分析:

可以看到,设置了daemon=True后,并没有执行完正在sleep中的counter_process进程,而是随着main_process的结束而终止了。

(2)join:join的作用是阻塞当前进程,直到调用join的那个进程执行完它的运算,回到当前进程下继续执行当前进程。

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2. import time  
  3.   
  4. class CounterProcess(multiprocessing.Process):  
  5.     def __init__(self, ts, arr):  
  6.         multiprocessing.Process.__init__(self)  
  7.         self.ts = ts  
  8.         self.arr = arr  
  9.   
  10.     def run(self):  
  11.         time.sleep(self.ts)  
  12.         sum = 0  
  13.         for i in self.arr:  
  14.             sum += i  
  15.         print 'sum = ' + str(sum)  
  16.   
  17.         c_time_cur_loc = time.localtime(time.time())  
  18.         counter_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  19.             c_time_cur_loc.tm_year, \  
  20.             c_time_cur_loc.tm_mon, \  
  21.             c_time_cur_loc.tm_mday, \  
  22.             c_time_cur_loc.tm_hour, \  
  23.             c_time_cur_loc.tm_min, \  
  24.             c_time_cur_loc.tm_sec \  
  25.             )  
  26.         print 'counter_process finished at ' + str(counter_timestamp)  
  27.   
  28. if __name__ == '__main__':  
  29.     ms_time_cur_loc = time.localtime(time.time())  
  30.     main_s_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  31.         ms_time_cur_loc.tm_year, \  
  32.         ms_time_cur_loc.tm_mon, \  
  33.         ms_time_cur_loc.tm_mday, \  
  34.         ms_time_cur_loc.tm_hour, \  
  35.         ms_time_cur_loc.tm_min, \  
  36.         ms_time_cur_loc.tm_sec \  
  37.         )  
  38.     print 'main_process started at ' + str(main_s_timestamp)  
  39.   
  40.     arr = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]  
  41.     ts = 2  
  42.     counter = CounterProcess(ts, arr)  
  43.     counter.daemon = True  
  44.     counter.start()  
  45.     counter.join()  
  46.   
  47.   
  48.     for i in arr:  
  49.         print 'arr.member = ' + str(i)  
  50.   
  51.     me_time_cur_loc = time.localtime(time.time())  
  52.     main_e_timestamp = '%04d%02d%02d_%02d%02d%02d' % ( \  
  53.         me_time_cur_loc.tm_year, \  
  54.         me_time_cur_loc.tm_mon, \  
  55.         me_time_cur_loc.tm_mday, \  
  56.         me_time_cur_loc.tm_hour, \  
  57.         me_time_cur_loc.tm_min, \  
  58.         me_time_cur_loc.tm_sec \  
  59.         )  
  60.     print 'main_process finished at ' + str(main_e_timestamp)</span>  

运行结果:


分析:

在本次执行的时候加入了主进程开始执行的时间,然后可以发现当在主进程中join了counter_process之后,就阻塞了当前正在运行的主进程,花了两秒时间完成了counter_process的运算,然后才继续进行main_process的运算,直到结束。

四、Lock

既然是并发,一定就会有lock来控制多进程访问共用资源的情况,python中锁有两种状态:被锁(locked)和没有被锁(unlocked),拥有acquire( )和release( )两种方法,并且遵循以下的规则:

1、unlocked的锁 + acquire( ) = locked的锁

2、locked的锁 + acquire( ) = 调用acquire( )的进程将进入阻塞,直到其他进程调用release( )方法释放锁

3、unlocked的锁 + release( ) = 抛出RuntimeError异常

4、locked的锁 + release( ) = 将该锁的状态由locked转变成unlocked

感谢yoyzhou提供了一张很清晰的acquire( )和release( )的逻辑图,引用如下所示:

另外:锁(Lock)可以和"with"语句一起使用,锁可以作为上下文管理器(context manager)。

使用with的好处是:当程序执行到"with"语句的时候,acquire( )方法将被调用,当程序执行完"with"语句时,release( )方法将被调用。这样我们就不用显示的调用acqiure( )和release( )方法,而是由with语句根据上下文来管理锁的获取和释放。

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2.   
  3. file_strA = 'file_writerA is working'  
  4. file_strB = 'file_writerB is working'  
  5.   
  6. def file_writerA(lock, file_path):  
  7.     print 'file_writerA process started already.'  
  8.     with lock:  
  9.         fs = open(file_path, 'a+')  
  10.         repeat_times = 1000000  
  11.         print 'file_writerA start to write.'  
  12.         while repeat_times >= 1:  
  13.             fs.write(file_strA + '\n')  
  14.             repeat_times -= 1  
  15.         print 'file_writerA finished writing.'  
  16.         fs.close()  
  17.   
  18.   
  19. def file_writerB(lock, file_path):  
  20.     print 'file_writerB process started already.'  
  21.     lock.acquire()  
  22.     try:  
  23.         fs = open(file_path, 'a+')  
  24.         repeat_times = 1000000  
  25.         print 'file_writerB start to write.'  
  26.         while repeat_times >= 1:  
  27.             fs.write(file_strB + '\n')  
  28.             repeat_times -= 1  
  29.         print 'file_writerB finished writing.'  
  30.         fs.close()  
  31.     finally:  
  32.         lock.release()  
  33.   
  34. if __name__ == "__main__":  
  35.     mdr_lock = multiprocessing.Lock()  
  36.     file_path = "E:\\file.txt"  
  37.     proc_writerA = multiprocessing.Process(target=file_writerA, args=(mdr_lock, file_path))  
  38.     proc_writerB = multiprocessing.Process(target=file_writerB, args=(mdr_lock, file_path))  
  39.     proc_writerA.start()  
  40.     proc_writerB.start()  
  41.     print "main_process is finished."</span>  

运行结果:


分析:

上面程序中分别使用手动acquire( )/release( )和with两种写法控制两个进程去写相同的文件。

通过上面的运行结果可以看到,当file_writerA process启动以后,锁住了文件,此时file_writerB process也启动了,但是由于A没有完成写文件,所以B被阻塞,当A完成了写操作以后,B才开始继续执行自己的写文件命令。

 

另外需要强调一点,指定相同target的不同进程仍然是不同进程,也会被acquire阻塞住的,如下面实验所示。

[python] view plain copy
 
  1. <span style="font-size:14px;">import multiprocessing  
  2. import threading  
  3.   
  4. file_strA = 'file_writerA is working'  
  5. file_strB = 'file_writerB is working'  
  6.   
  7. def file_writerA(name, lock, file_path):  
  8.     print 'file_writer process ' + name + ' started already.'  
  9.     print 'inname = ' + multiprocessing.current_process().name  
  10.     with lock:  
  11.         fs = open(file_path, 'a+')  
  12.         repeat_times = 1000000  
  13.         print 'file_writer ' + name + ' start to write.'  
  14.         while repeat_times >= 1:  
  15.             fs.write(file_strA + '\n')  
  16.             repeat_times -= 1  
  17.         print 'file_writer ' + name + ' finished writing.'  
  18.         fs.close()  
  19.   
  20. def file_writerB(name, lock, file_path):  
  21.     print 'file_writer process ' + name + ' started already.'  
  22.     print 'inname = ' + multiprocessing.current_process().name  
  23.     lock.acquire()  
  24.     try:  
  25.         fs = open(file_path, 'a+')  
  26.         repeat_times = 1000000  
  27.         print 'file_writer ' + name + ' start to write.'  
  28.         while repeat_times >= 1:  
  29.             fs.write(file_strB + '\n')  
  30.             repeat_times -= 1  
  31.         print 'file_writer ' + name + ' finished writing.'  
  32.         fs.close()  
  33.     finally:  
  34.         lock.release()  
  35.   
  36. if __name__ == "__main__":  
  37.     mdr_lock = multiprocessing.Lock()  
  38.     file_path = "E:\\file.txt"  
  39.     proc_writerA1 = multiprocessing.Process(target=file_writerA, args=('A1', mdr_lock, file_path,))  
  40.     proc_writerA2 = multiprocessing.Process(target=file_writerA, args=('A2', mdr_lock, file_path,))  
  41.     proc_writerB = multiprocessing.Process(target=file_writerB, args=('B', mdr_lock, file_path,))  
  42.     proc_writerA1.start()  
  43.     proc_writerA2.start()  
  44.     proc_writerB.start()  
  45.     print 'main_process is finished.'  
  46. </span>  

运行结果:


分析:

上面的实验可以看到,proc_writerA1和pro_writerA2都是指定targer=file_writerA,但是从运行结果上我们看到A2被阻塞到A1和B都执行完毕才开始执行自己的写操作。

五、RLock

RLock是可重入锁(reetrant lock),和Lock对比:

(1)相同之处:当某一个进程lock.acquire( )后,直到其释放前,其他所有acquire相同lock的进程将被阻塞(包括自身进程)。

(2)区别之处:是同一个进程能够不被阻塞的多次调用rlock.acquire( ),同样需要相等次数的release( )才能释放后,其他进程才可以结束acquire的阻塞。

 

 

 

 

参考文献:

http://www.cnblogs.com/kaituorensheng/p/4445418.html

http://www.cnblogs.com/lipijin/p/3709903.html

http://yoyzhou.github.io/blog/2013/02/28/python-threads-synchronization-locks/

 

posted on 2018-05-17 17:41  ExplorerMan  阅读(678)  评论(0)    收藏  举报

导航