转载

2016/1/2 Python中的多线程(1):线程初探

---恢复内容开始---

新年第一篇,继续Python。

先来简单介绍线程和进程。

计算机刚开始发展的时候,程序都是从头到尾独占式地使用所有的内存和硬件资源,每个计算机只能同时跑一个程序。后来引进了一些机制来改进这种调用方法,包括流水线,多进程。我们开始并发执行程序,每个程序交替地被处理器调用,再极高的频率下,你会认为这些程序是在同时执行的,这也就是并发技术。用操作系统来管理并发,将程序读到内存中,然后被操作系统调用开始,它的生命周期就开始了。而每个程序的执行,都是用进程的方式执行,每个进程有自己的地址空间、内存、数据栈及别的什么东西。对于任何一个以进程方式执行的程序,都好似独占了全部的硬件资源。每个进程都有从地址0开始的虚拟内存地址。也就是说,进程是一定意义的抽象。然后操作系统管理所有的进程,给它们分配分时运行的时间。进程之间通过一定的进程间交互手段来通信,不能直接共享信息。

那什么是线程?

线程常常被称为轻量级进程,跟进程有些相似,不同的是所有的线程都运行在同一个进程中,共享同样的运行环境,有同样的地址空间、数据栈空间。可以认为在一个进程里,有很多并行执行的线程。

线程有开始,顺序执行和结束三个部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占或者暂时挂起,让其他线程运行,这种方式叫做让步。

线程间由于共享了同样的数据空间,所以可以很方便地共享数据和通讯,但是,有一个问题是多个线程同时访问同一片数据,由于顺序不同,可能数据结果会不一致,产生了所谓的竞态条件。

总的来说,线程是在一个程序里设置的几个并发运行的过程,让几件事情可以同时执行。

Python中使用线程

Python的代码由Python虚拟机控制,可以通过全局解释器锁GIL来控制,也可以直接用一些模块来实现我们的需求。接下来主要来介绍thread和threading模块。thread模块一般不推荐使用,因为它在主线程退出时,其他线程若没有结束,还没有清除就退出了,而threading模块确保所有的重要的子线程都退出后才会结束进程。

默认情况下,Python对线程的支持是打开的。在交互模式下尝试导入thread模块没有错误就表示可用。

 >>> import thread >>> 

如果出现了导入错误,那么应该重新编译Python解释器才能运行,这里不作说明。

先来看一个没有多线程支持例子:

这里用了time模块的sleep()函数,里面输入一个浮点的参数,表示睡眠的秒数,意味着程序将被挂起这段时间。

 from time import sleep, ctime  def loop0():     print 'start loop 0 at: %s' % ctime()     sleep(4)     print 'loop 0 done at: %s' % ctime()  def loop1():     print 'start loop 1 at: %s' % ctime()     sleep(2)     print 'loop 1 done at: %s' % ctime()  def main():     print 'starting at: %s' % ctime()     loop0()     loop1()     print 'all Done at: %s' % ctime()  if __name__ == '__main__':     main() 
 >>>  starting at: Sat Jan 02 21:17:48 2016 start loop 0 at: Sat Jan 02 21:17:48 2016 loop 0 done at: Sat Jan 02 21:17:52 2016 start loop 1 at: Sat Jan 02 21:17:52 2016 loop 1 done at: Sat Jan 02 21:17:54 2016 all Done at: Sat Jan 02 21:17:54 2016 

可以看到,程序毫无疑问的顺序执行了,但是,我们用sleep()挂起的时间并没有意义了。

所以,让我们看一下用了线程之后的方法:

 import thread from time import sleep, ctime  def loop0():     print 'start loop 0 at: %s' % ctime()     sleep(4)     print 'loop 0 done at: %s' % ctime()  def loop1():     print 'start loop 1 at: %s' % ctime()     sleep(2)     print 'loop 1 done at: %s' % ctime()  def main():     print 'starting at: %s' % ctime()     thread.start_new_thread(loop0,())     thread.start_new_thread(loop1,())     sleep(6)     print 'all Done at: %s'% ctime()  if __name__ == '__main__':     main()  

结果会是这样的

 >>>  starting at: Sat Jan 02 21:23:58 2016 start loop 0 at: Sat Jan 02 21:23:58 2016 start loop 1 at: Sat Jan 02 21:23:58 2016 loop 1 done at: Sat Jan 02 21:24:00 2016 loop 0 done at: Sat Jan 02 21:24:02 2016 all Done at: Sat Jan 02 21:24:04 2016 

可以看到,这一次loop1和loop0在程序开始后4秒就结束了,只是我们多了一句sleep(6),让整个程序最后还是跑了6秒,加这一句,是防止主线程结束后子线程也就退出了,导致根本没有执行完,但是这种方法实在是很愚蠢,我们最后程序还是跑了6秒才能结束,如果我们有一次并不知道子进程什么时候结束,比如说从键盘读到一条命令后结束,那么该如何写这样的语句呢。接下来我会介绍这种方法,我们先来看看thread这个模块在此处干了什么。

我们调用了thread的一个方法start_new_thread(funciton, args kwargs=None),这个方法的作用是产生一个新线程,在新线程中用指定的参数和可选的kwargs来调用这个函数。这是一个很简单的线程机制。

接下来用锁来杜绝在主线程中使用sleep()函数:

 import thread from time import ctime, sleep  loops = [4, 2]  def loop(nloop, nsec, lock):     print 'start loop%s at: %s/n' % (nloop, ctime()),     sleep(nsec)     print 'loop%s done at: %s/n' % (nloop, ctime()),     lock.release()  def main():     print 'starting at: %s/n' % ctime(),     locks = []     nloops = range(len(loops))      for i in nloops:         lock = thread.allocate_lock()         lock.acquire()         locks.append(lock)      for i in nloops:         thread.start_new_thread(loop, (i, loops[i], locks[i]))      for i in nloops:         while locks[i].locked():             pass      print 'all DONE at: %s/n' %ctime(),  if __name__ == '__main__':     main() 

显示结果是这样的:

 >>>  starting at: Sat Jan 02 21:55:30 2016 start loop0 at: Sat Jan 02 21:55:30 2016 start loop1 at: Sat Jan 02 21:55:30 2016 loop1 done at: Sat Jan 02 21:55:32 2016 loop0 done at: Sat Jan 02 21:55:34 2016 all DONE at: Sat Jan 02 21:55:34 2016 

大家可以看到我用了一种很抽风的方式使用print语句,至于为什么不用基本的方法,各位可以看一下用原来的方法输出结果是怎样的。

今天先写到这里,下一次再说明threading的使用。

正文到此结束
Loading...