天天看点

python网络编程之多线程编程

线程与进程

进程是执行中的一个程序,每个进程都拥有自己的地址空间、内存、数据栈以及其它的辅助数据。而线程可以称为轻量级进程,位于进程内,共享上下文地址。

全局解释器锁

python通过引用计数进行内存管理,每个python对象都具有引用计数的变量。当对象的引用计数为0时,就释放该对象占用的内存空间。为了防止有多个线程同时对对象的引用计数进行更改,便引入了全局解释器锁。因此虽然python解释器支持多线程编程,但由于全局解释器锁的存在,在任意时刻,只有一个线程可以处于执行状态。这种情况会对计算繁忙型的程序产生较大的影响。

python中的线程模块

python在绝大多数类UNIX操作平台(如linux,mac osx)以及Windows上是支持多线程编程的。python中的多线程编程模块有thread(python3中是_thread)、threading以及Queue模块。

thread模块提供了基本的线程和锁定支持;threading模块提供了高级的线程管理(在实际使用中,尽量使用threading模块而不是thread模块,因为threading模块更加先进,有更好的线程控制,同步原语也比thread模块多,另外,thread模块对线程的何时退出没有进行控制,当主线程结束时,其它所有线程无论完成与否,都同时结束);使用 Queue 模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。

未使用多线程的代码
from time import sleep,ctime
def loop_0():
  print("start loop 0 at:",ctime())
  sleep(4)
  print("loop 0 done at:",ctime()) 
def loop_1():
  print('start loop 1 at:',ctime())
  sleep(2)
  print("loop 1 done at:", ctime())
def main():
  print("starting at:",ctime())
  loop_0()
  loop_1()
  print("all done at:",ctime())
  
if __name__ =="__main__":
  main()

           

运行结果如下:

starting at: Sun Oct 27 08:25:58 2019
start loop 0 at: Sun Oct 27 08:25:58 2019
loop 0 done at: Sun Oct 27 08:26:02 2019
start loop 1 at: Sun Oct 27 08:26:02 2019
loop 1 done at: Sun Oct 27 08:26:04 2019
all done at: Sun Oct 27 08:26:04 2019
           

可以看出,在不使用多线程的情况下,程序是依次执行的,总共耗时6秒。

使用thread多线程
import _thread
from time import sleep,ctime

def loop_0():
  print("start loop 0 at:",ctime())
  sleep(4)
  print("loop 0 done at:",ctime())
  
def loop_1():
  print('start loop 1 at:',ctime())
  sleep(2)
  print("loop 1 done at:", ctime())
def main():
  print("starting at:",ctime())
  _thread.start_new_thread(loop_0,())
  _thread.start_new_thread(loop_1,())
  sleep(4)  #根据thread的特性,主线程结束时会直接强制结束其它线程,因此设置了休眠时间,自己可以试试把该语句注释掉会是什么结果
  print("all done at:",ctime())

if __name__ =="__main__":
  main()
           

运行结果如下所示

starting at: Sun Oct 27 08:28:08 2019
start loop 1 at: Sun Oct 27 08:28:08 2019
start loop 0 at: Sun Oct 27 08:28:08 2019
loop 1 done at: Sun Oct 27 08:28:10 2019
all done at: Sun Oct 27 08:28:12 2019
loop 0 done at: Sun Oct 27 08:28:12 2019
           

多线程的执行时间为4秒,比单线程的执行时间少。由于注释中提到的thread的特性,为了保证程序正确执行,需要确保在主线程执行结束时,所有其它子线程均执行结束。因此,在使用thread模块进行多线程编程时,需要额外编写语句去确认其它子线程是否执行完毕。

使用threading多线程

threading模块支持守护线程,如果把一个线程设置为守护线程,就说明该线程是不重要的,主线程结束时不需等待该线程执行结束。同样的,只有当其它非守护线程执行结束时,主线程才能结束。

同样的多线程例子,使用threading模块的代码如下所示。

import threading
from time import sleep,ctime

loops = [4,2]
def loop(nloop,nsec):
  print("start loop {} at:{}".format(nloop,ctime()))
  sleep(nsec)
  print("loop {} done at:{}".format(nloop,ctime()))
def main():
  print('starting at :',ctime())
  threads = []
  nloops = range(len(loops))
  for i in nloops:
    t = threading.Thread(target=loop,args=(i,loops[i]))
    threads.append(t)
    
  for i in nloops:
    threads[i].start()
    
  for i in nloops:
    threads[i].join()
    
  print("all done at:",ctime())
  
if __name__ == '__main__':
  main()
           

运行结果如下

starting at : Sun Oct 27 09:03:40 2019
start loop 0 at:Sun Oct 27 09:03:40 2019
start loop 1 at:Sun Oct 27 09:03:40 2019
loop 1 done at:Sun Oct 27 09:03:42 2019
loop 0 done at:Sun Oct 27 09:03:44 2019all done at: 
Sun Oct 27 09:03:44 2019
           

该方法先实例化Thread对象,并将函数和参数传递进去,然后得到实例化对象,得到的对象并不会立即开始执行,而是在调用start之后才开始执行。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间才会执行完毕。

继续阅读