Chinaunix首页 | 论坛 | 博客
  • 博客访问: 77095
  • 博文数量: 36
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-10-08 13:58
文章分类
文章存档

2015年(36)

我的朋友

分类: Python/Ruby

2015-10-08 22:17:06

     时隔月余,开始熟悉新的环境,一切也慢慢回到正轨,我们的笔记也要跟着回归了。未来几天我们来看看使用Python实现并发编程,首先今天我们来看一下并发进程编程。首先简单介绍一下并发运行的基本概念吧!
一、准备
     并发编程,顾名思义就是指的计算机同时运行程序,在过去的单一CPU上,其实CPU使用的是时间分片的形式来保证多个任务的“同时”运行,这里之所以特别标注,是因为单个CPU上的并行指的是多个程序轮流获得CPU分配的时间片运行,换句话说,CPU将时间分成了连续的时间片,每个时间片用于执行一个程序的一部分,然后再切换到下一个时间片,依次往复循环,由于CPU的时间片非常小,切换速度极快,因此给我们的感觉就是计算机同时在运行多个程序,即多任务。现在的CPU基本都进入了多核时代,比如i5-i7处理器,就是四核处理器,运行时会在每个CPU上分配单独的一个进程,实现了真正意义上的并行执行程序。
     再来说说进程和线程的概念,其实进程是程序资源的容器,里面包含了程序运行所有需要的数据,包括代码、变量以及程序指针等;而线程是在进程内部建立的执行逻辑,共享进程的所有资源,但是却有自己的程序计数器,即执行流程。可以说线程是轻量性的进程,但是却是共享资源。并发编程会显著地提高效率,但是也会引发一系列问题,最典型的就是“竞争问题”。
     比如Jason Whittington曾经讲过一个笑话:
     问:为什么多线程的小鸡要过马路?
     答:要另边,去一。
     
对于Python而言,同时支持消息传递和基于线程的并发编程,但是对于线程有着诸多限制,最主要的莫过于GIL(Global Interpreter Lock),即全局解释器锁定,这项机制使得在任意指定的时刻只允许单个Python线程运行,因此如果使用线程并发操作,无论系统上存在多少个可用的CPU核心,都使得只能在一个CPU上运行单线程。尽管GIL现在是社区中争论的焦点,但是最近一段时间都不会消失。当然,线程并发可以用于与I/O绑定的情况,这时往往需要等待I/O返回结果,因此单线程的调度执行时间是可以等待的;但是除此之外,更多的时候使用并发进程更好一些。下面我们就来简单介绍一下Python中的并发进程编程。

二、进程并发
     Python中提供了multiprocessing模块用于处理并发进程的相关操作,下面我们按照老样子,通过一个具体的程序实例来了解其基本的应用,我觉得这样要好于上来就看一些API的解释。

点击(此处)折叠或打开

  1. #This is a test for multiprocessing...
  2. import multiprocessing
  3. import time

  4. def clock_1(interval):
  5.     while True:
  6.         print "--1--: The time is : ", time.ctime()
  7.         time.sleep(interval)

  8. def clock_2(interval):
  9.     while True:
  10.         print '--2--: The time is: ', time.ctime()
  11.         time.sleep(interval)


  12. if __name__ == '__main__':
  13.     #can't run in Windows without it, subprocess
  14.     #must be created by MainProcess
  15.     print 'This is a test for multiprocessing...'
  16.     p1 = multiprocessing.Process(target=clock_1, args=(3,))
  17.     p1.start()
  18.     p2 = multiprocessing.Process(target=clock_2, args=(4,))
  19.     p2.start()
  20.     print 'SubProcess is going to start...'
  21.     time.sleep(16)
  22.     print 'SubProcess is going to be shutdown...'
  23.     p1.terminate()
  24.     print 'Clock_1 has been terminated...'
  25.     p2.terminate()
  26.     print 'Clock_2 has been terminated...'
  27.     print '1-Pid is ', p1.pid
  28.     print '2-SubProcess\'name is ', p2.name
  29.     print '2-ExitCode is ', p2.exitcode
  30.     raw_input('Enter for Exit...')
      这是我们的第一个关于并发进程的程序,第2行和第3行首先要导入我们需要的功能库,multiprocessing就是我们的并发进程库,time库则用来提供答应系统时间和休眠函数。
-1. 第5-8行定义了我们的第一个子进程函数,用来打印当前的系统时间,循环打印后,休眠指定的间隔;
-2. 第10-13行是类似的功能,注意两个子进程使用了time.ctime()用来答应当前的系统时间(其实是将秒计数convert成了我们熟悉的时间格式);
-3. 第16行的if __name__ == '__main__'表示当双击该代码时,自动调用命令窗口Main函数执行下面的代码部分;
-4. 第20行调用了multiprocessing.Process类的构造函数创建了一个子进程,这里需要特别注意的是参数target=*用来指定子进程启动的函数代码,而参数args=*接收一个参数序列,如果只有一个参数,必须使用','来表示序列后部分为空,该类方法返回子进程对象p1;
-5. 第21行调用子进程对象的start()方法启动该进程,类似的还有terminate()方法用于强行终止进程(不负责清理,所以不建议直接使用),以及name\exitcode\pid等属性;
-6. 第22-23行依次创建子进程2;
-7. 第27和29行终止两个子进程;
     这个并发程序结构需要特别注意,即为了保证程序的跨平台性,必须由主进程创建子进程,在Unix上这是可选的,但是在Windows上则是必须的,而且在Windows系统上IDLE运行RUN命令不能够创建子进程,必须使用CMD才可以。好了,我们来看看这个程序的测试结果:


三、进程间通信
     仅仅是并发进程是不够的,更重要的是这些进程可以协同工作,要协同工作就必然要进行通信以确定对方工作的进度,从而给予配合。Python下进程间通信有消息队列和管道两种机制,今天我们这里使用管道来作为进程间通信的讲解例子,因为二者使用方法类似,但是个人感觉管道更简单一些,双向通信,端点也易于管理。
     还是先来看看我们这里的代码吧:

点击(此处)折叠或打开

  1. This is a test for Pipe...
  2. #The test is like this: Client send x and y, Server return x+y as a result...
  3. import multiprocessing
  4. import time

  5. def adder(pipe):
  6.     print 'Server SubProcess start...'
  7.     server_p, client_p = pipe
  8.     client_p.close()
  9.     while True:
  10.         try:
  11.             x, y = server_p.recv()
  12.         except EOFError:
  13.             print 'EOFError...'
  14.             time.sleep(5)
  15.             break
  16.         result = x + y
  17.         server_p.send(result)
  18.     print "Server done..."

  19. if __name__ == '__main__':
  20.     (server_p, client_p) = multiprocessing.Pipe()
  21.     print 'Pipe Created...'
  22.     time.sleep(3)
  23.     adder_p = multiprocessing.Process(target=adder, args=((server_p, client_p),))
  24.     adder_p.start()
  25.     server_p.close()

  26.     print 'Begin to test...'
  27.     client_p.send((9, 9))
  28.     time.sleep(1)
  29.     print client_p.recv()

  30.     client_p.send(('You','UP!'))
  31.     print client_p.recv()

  32.     time.sleep(5)
  33.     client_p.close()
  34.     #if MainProcess shutdown, the SubProcess created by it will also be shutdown
  35.     #So U should wait until the SubProcess done...
  36.     adder_p.join()

  37.     raw_input('Enter for Exit...')
      程序虽然不长,但是足够我们对于管道机制管中窥豹了。这个程序是建立一个服务器子进程,用于接收客户端子进程提交的数据,将其做求和处理,下面我们逐一说明。
-1. 第3-4行导入了我们需要的模块:multiprocessing和time,这里的time主要用来打印调试信息,这里的程序版本也有一些调试信息,实际代码中都可以注释掉;
-2. 第6-19行定义了一个服务器加法处理器函数,作为Process类构造函数的target参数;第8行将输入参数序列pipe的值分别赋值给两个变量:server_p和client_p,这其实就是管道的两个端口,默认为双向管道,每个端口都可以发送和读取数据;
-3. 第9行用于关闭服务器子进程不适用的客户端口,即client_p,这样易于管道的管理;
-4. 第10-18行进入到while循环,读取管道另一端发来的数据,将其求和作为result返回,写入管道,注意这里的读取管道使用的是server_p.recv()读取一个序列化对象,而server_p.send()则用来发送一个序列化对象,记得这里的对象都是序列化的;
-5. 第21行定义CMD形式;
-6. 第22行利用multprocessing.Pipe()类构造一个管道对象,这里要注意这步必须在创建两个子进程之前;
-7. 第25行继续使用multprocessing.Process()构造一个子进程对象,其中这里的参数args本身就是一个序列化,必须使用((server_p, client_p),)才可以,否则程序会出现错误;
-8. 第26行启动adder_p子进程,同时关闭不用的服务端端点server_p.close();
-9. 第30行用于发送一个序列数据:client_p.send((9, 9)),第32行使用client_p.recv()接收服务端管道的结果;
-10. 第38行使用完之后关闭客户端的管道端点:client_p.close()
-11. 第41行的adder_p.join()方法很重要,该方法要求主进程等待其子进程返回结果后再关闭,避免主进程关闭时子进程尚在处理数据而一起关闭;
     说了这么多,会不会有些晕呢?好了,我们直接来看看程序运行的结果,这个比较直观:

     服务端进程中的while循环的try-except用于判断当没有客户端的输入时,服务端跳出循环,自己猜测是在client_p.close()时会发送关闭信号,导致服务端的EOF错误;这里需要注意直接双击的话无法看到结果,服务端会持续等待:

自己使用了PowerShell将输入导入文件log.txt查看到了错误信息,可以用于程序调试:

     使用PowerShell运行结果:


【PS:
     最后简单小结一下吧:
1. 并发进程模块multiprocessing的使用,其中创建子进程类Process和管道类Pipe;
2. 子进程args参数是一个序列参数,因此记得使用(,)表示后面为空序列;
3. 管道的端点默认都是双向通信的,为了便于管理,必须关闭不用的另一个端点;
4. 多进程程序需要subprocess.join()等待; 
5. 管道使用结束时需要主动关闭,以告知对端结束;】

阅读(770) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~