详解Python中的多线程编程

一、简介

多线程编程技术可以实现代码并行性,优化处理能力,同时功能的更小划分可以使代码的可重用性更好。Python中threading和Queue模块可以用来实现多线程编程。
二、详解
1、线程和进程
       进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。进程也可以通过fork和spawn操作来完成其它的任务,不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。
       线程(有时被称为轻量级进程)跟进程有些相似,不同的是所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。线程有开始、顺序执行和结束三部分,它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(也叫睡眠)让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其它的线程共享运行的结果。多个线程共同访问同一片数据不是完全没有危险的,由于数据访问的顺序不一样,有可能导致数据结果的不一致的问题,这叫做竞态条件。而大多数线程库都带有一系列的同步原语,来控制线程的执行和数据的访问。
2、使用线程
(1)全局解释器锁(GIL)
       Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
       对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按以下方式执行:a、设置 GIL;b、切换到一个线程去运行;c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));d、把线程设置为睡眠状态;e、解锁 GIL;d、再次重复以上所有步骤。
        在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
(2)退出线程
       当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,不可以直接“杀掉”("kill")一个线程。
        不建议使用thread模块,很明显的一个原因是,当主线程退出的时候,所有其它线程没有被清除就退出了。另一个模块threading就能确保所有“重要的”子线程都退出后,进程才会结束。
(3)Python的线程模块
       Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
       避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
3、thread模块
       thread模块除了产生线程外,thread模块也提供了基本的同步数据结构锁对象(lock object也叫原语锁、简单锁、互斥锁、互斥量、二值信号量)。同步原语与线程的管理是密不可分的。
        常用的线程函数以及LockType类型的锁对象的方法:

  #!/usr/bin/env python 

  import thread
  from time import sleep, ctime 

  def loop0():
    print '+++start loop 0 at:', ctime()
    sleep(4)
    print '+++loop 0 done at:', ctime() 

  def loop1():
    print '***start loop 1 at:', ctime()
    sleep(2)
    print '***loop 1 done at:', ctime() 

  def main():
    print '------starting at:', ctime()
    thread.start_new_thread(loop0, ())
    thread.start_new_thread(loop1, ())
    sleep(6)
    print '------all DONE at:', ctime() 

  if __name__ == '__main__':
    main()

thread 模块提供的简单的多线程的机制,两个循环并发地被执行,总的运行时间为最慢的那个线程的运行时间(主线程6s),而不是所有的线程的运行时间之和。start_new_thread()要求要有前两个参数,就算想要运行的函数不要参数,也要传一个空的元组。

sleep(6)是让主线程停下来,主线程一旦运行结束,就关闭运行着其他两个线程。但这可能造成主线程过早或过晚退出,那就要使用线程锁,可以在两个子线程都退出后,主线程立即退出。
在CODE上查看代码片派生到我的代码片

  #!/usr/bin/env python 

  import thread
  from time import sleep, ctime 

  loops = [4, 2] 

  def loop(nloop, nsec, lock):
    print '+++start loop:', nloop, 'at:', ctime()
    sleep(nsec)
    print '+++loop:', nloop, 'done at:', ctime()
    lock.release() 

  def main():
    print '---starting threads...'
    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:', ctime() 

  if __name__ == '__main__':
    main()

4、threading模块
        更高级别的threading模块,它不仅提供了Thread类,还提供了各种非常好用的同步机制。threading 模块里所有的对象:

thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果主线程退出不用等待那些子线程完成,那就设定这些线程的daemon属性,即在线程thread.start()开始前,调用setDaemon()函数设定线程的daemon标志(thread.setDaemon(True))就表示这个线程“不重要”。如果想要等待子线程完成再退出,那就什么都不用做或者显式地调用thread.setDaemon(False)以保证其daemon标志为False,可以调用thread.isDaemon()函数来判断其daemon标志的值。新的子线程会继承其父线程的daemon标志,整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。
(1)threading的Thread类
       它有很多thread模块里没有的函数,Thread对象的函数:

创建一个Thread的实例,传给它一个函数
在CODE上查看代码片派生到我的代码片

  #!/usr/bin/env python 

  import threading
  from time import sleep, ctime 

  loops = [ 4, 2 ] 

  def loop(nloop, nsec):
    print '+++start loop:', nloop, 'at:', ctime()
    sleep(nsec)
    print '+++loop:', nloop, 'done at:', 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:      # start threads
      threads[i].start() 

    for i in nloops:      # wait for all
      threads[i].join()    # threads to finish 

    print '---all DONE at:', ctime() 

  if __name__ == '__main__':
    main()

实例化一个Thread(调用 Thread())与调用thread.start_new_thread()之间最大的区别就是,新的线程不会立即开始。在创建线程对象,但不想马上开始运行线程的时候,这是一个很有用的同步特性。所有的线程都创建了之后,再一起调用 start()函数启动,而不是创建一个启动一个。而且也不用再管理一堆锁(分配锁、获得锁、释放锁、检查锁的状态等),只要简单地对每个线程调用join()主线程等待子线程的结束即可。join()还可以设置timeout的参数,即主线程等到超时为止。
        join()的另一个比较重要的方面是它可以完全不用调用,一旦线程启动后,就会一直运行,直到线程的函数结束,退出为止。如果主线程除了等线程结束外,还有其它的事情要做,那就不用调用 join(),只有在等待线程结束的时候才调用join()。
创建一个Thread的实例,传给它一个可调用的类对象
[html] view plaincopy在CODE上查看代码片派生到我的代码片

  #!/usr/bin/env python 

  import threading
  from time import sleep, ctime 

  loops = [ 4, 2 ] 

  class ThreadFunc(object): 

    def __init__(self, func, args, name=''):
      self.name = name
      self.func = func
      self.args = args 

    def __call__(self):
      apply(self.func, self.args) 

  def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'done at:', ctime() 

  def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops)) 

    for i in nloops:  # create all threads
      t = threading.Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__))
      threads.append(t) 

    for i in nloops:  # start all threads
      threads[i].start() 

    for i in nloops:  # wait for completion
      threads[i].join() 

    print 'all DONE at:', ctime() 

  if __name__ == '__main__':
    main()

与传一个函数很相似的另一个方法是在创建线程的时候,传一个可调用的类的实例供线程启动的时候执行,这是多线程编程的一个更为面向对象的方法。相对于一个或几个函数来说,类对象里可以使用类的强大的功能。创建新线程的时候,Thread对象会调用ThreadFunc对象,这时会用到一个特殊函数__call__()。由于已经有了要用的参数,所以就不用再传到Thread()的构造函数中。由于有一个参数的元组,这时要使用apply()函数或使用self.res = self.func(*self.args)。
从Thread派生出一个子类,创建一个这个子类的实例
在CODE上查看代码片派生到我的代码片

  #!/usr/bin/env python 

  import threading
  from time import sleep, ctime 

  loops = [ 4, 2 ] 

  class MyThread(threading.Thread):
    def __init__(self, func, args, name=''):
      threading.Thread.__init__(self)
      self.name = name
      self.func = func
      self.args = args 

    def getResult(self):
      return self.res 

    def run(self):
      print 'starting', self.name, 'at:', ctime()
      self.res = apply(self.func, self.args)
      print self.name, 'finished at:', ctime() 

  def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'done at:', ctime() 

  def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops)) 

    for i in nloops:
      t = MyThread(loop, (i, loops[i]),
      loop.__name__)
      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() 

子类化Thread类,MyThread子类的构造函数一定要先调用基类的构造函数,特殊函数__call__()在子类中,名字要改为run()。在 MyThread类中,加入一些用于调试的输出信息,把代码保存到myThread模块中,并导入这个类。除使用apply()函数来运行这些函数之外,还可以把结果保存到实现的self.res属性中,并创建一个新的函数getResult()来得到结果。
(2)threading模块中的其它函数

5、Queue模块
       常用的 Queue 模块的属性:

Queue模块可以用来进行线程间通讯,让各个线程之间共享数据。Queue解决生产者-消费者的问题,现在创建一个队列,让生产者线程把新生产的货物放进去供消费者线程使用。生产者生产货物所要花费的时间无法预先确定,消费者消耗生产者生产的货物的时间也是不确定的。
在CODE上查看代码片派生到我的代码片

  #!/usr/bin/env python 

  from random import randint
  from time import sleep
  from Queue import Queue
  from myThread import MyThread 

  def writeQ(queue):
    print '+++producing object for Q...',
    queue.put('xxx', 1)
    print "+++size now:", queue.qsize() 

  def readQ(queue):
    val = queue.get(1)
    print '---consumed object from Q... size now', \
      queue.qsize() 

  def writer(queue, loops):
    for i in range(loops):
      writeQ(queue)
      sleep(randint(1, 3)) 

  def reader(queue, loops):
    for i in range(loops):
      readQ(queue)
      sleep(randint(2, 5)) 

  funcs = [writer, reader]
  nfuncs = range(len(funcs)) 

  def main():
    nloops = randint(2, 5)
    q = Queue(32) 

    threads = []
    for i in nfuncs:
      t = MyThread(funcs[i], (q, nloops), \
        funcs[i].__name__)
      threads.append(t) 

    for i in nfuncs:
      threads[i].start() 

    for i in nfuncs:
      threads[i].join() 

    print '***all DONE' 

  if __name__ == '__main__':
    main()

这个实现中使用了Queue对象和随机地生产(和消耗)货物的方式。生产者和消费者相互独立并且并发地运行,它们不一定是轮流执行的(随机数模拟)。writeQ()和readQ()函数分别用来把对象放入队列和消耗队列中的一个对象,在这里使用字符串'xxx'来表示队列中的对象。writer()函数就是一次往队列中放入一个对象,等待一会然后再做同样的事,一共做指定的次数,这个次数是由脚本运行时随机生成的。reader()函数做的事比较类似,只是它是用来消耗对象的。
6、线程相关模块
       多线程相关的标准库模块:

       三、总结
(1)一个要完成多项任务的程序,可以考虑每个任务使用一个线程,这样的程序在设计上相对于单线程做所有事的程序来说,更为清晰明了。
(2)单线程的程序在程序性能上的限制,尤其在有相互独立、运行时间不确定、多个任务的程序里,而把多个任务分隔成多个线程同时运行会比顺序运行速度更快。由于Python解释器是单线程的,所以不是所有的程序都能从多线程中得到好处。
(3)若有不足,请留言,在此先感谢!

(0)

相关推荐

  • python多线程threading.Lock锁用法实例

    本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 复制代码 代码如下: mutex = threading.Lock() #锁的使用 #创建锁 mutex = threading.Lock() #锁定 mutex.acquire([timeout]) #释放 mutex.release() 锁定方法acquire可以有一个超时时间的可选参数timeout.如果设定了timeout,则在超时后通过返回值

  • python简单分割文件的方法

    本文实例讲述了python简单分割文件的方法.分享给大家供大家参考.具体如下: 有的网站在上传文件时对文件大小有限制,因此可以将大文件分割成多个小文件再上传. #!/usr/bin/env python def split(filename, size): fp = open(filename, 'rb') i = 0 n = 0 temp = open(filename+'.part'+str(i),'wb') buf = fp.read(1024) while(True): temp.wri

  • Python实现分割文件及合并文件的方法

    本文实例讲述了Python实现分割文件及合并文件的方法.分享给大家供大家参考.具体如下: 分割文件split.py如下: #!/usr/bin/python ########################################################################## # split a file into a set of parts; join.py puts them back together; # this is a customizable ve

  • Python实现将一个大文件按段落分隔为多个小文件的简单操作方法

    本文实例讲述了Python实现将一个大文件按段落分隔为多个小文件的简单操作方法.分享给大家供大家参考,具体如下: 今天帮同学处理一点语料.语料文件有点大,并且是以连续两个换行符作为段落标志,他想把它按段落分隔成多个小文件,即每3个段落组成一个新文件.由于以前没有遇到过类似的操作,在网上找了一些相似的方法,看起来都有点复杂.所以经尝试,自己写了一段代码,完美解决问题. 基本思路是,先读原文件内容,并使用正则表达式,依据\n\n进行切片处理,结果为一个列表,其中每一个列表元素都存放一个切片中的内容;

  • python与php实现分割文件代码

    前两天有个朋友说,想实现一个文本文件按照固定行数进行分割成多个文本文件,却不知如何实现.如果数据量小手动分割下就好了,如果数据量很大的话手动完成实在太耗费人力了,也不现实.那么就需要借助脚本去实现.既然有朋友想简单的完成这个任务,那么不如记录下来,给需要的朋友提供方便. 下面我就分别使用python和php进行脚本的实现和操作,当然用其他语言都能实现,大家可根据对语言的熟悉程度进行自主选择,如果有朋友还没有达到编写代码的能力的话,那么最起码对语言环境的使用要会,只要达到这些,就可以完成如下工作.

  • Python threading多线程编程实例

    Python 的多线程有两种实现方法: 函数,线程类 1.函数 调用 thread 模块中的 start_new_thread() 函数来创建线程,以线程函数的形式告诉线程该做什么 复制代码 代码如下: # -*- coding: utf-8 -*- import thread def f(name):   #定义线程函数   print "this is " + name   if __name__ == '__main__':   thread.start_new_thread(f

  • Python实现模拟分割大文件及多线程处理的方法

    本文实例讲述了Python实现模拟分割大文件及多线程处理的方法.分享给大家供大家参考,具体如下: #!/usr/bin/env python #--*-- coding:utf-8 --*-- from random import randint from time import ctime from time import sleep import queue import threading class MyTask(object): """具体的任务类"&qu

  • 理解python多线程(python多线程简明教程)

    对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的MS-DOS时代,操作系统处理问题都是单任务的,我想做听音乐和看电影两件事儿,那么一定要先排一下顺序. (好吧!我们不纠结在DOS时代是否有听音乐和看影的应用.^_^) 复制代码 代码如下: from time import ctime,sleep def music():    for i in range(2):        prin

  • python分割文件的常用方法

    本文大家整理了一些比较好用的关于python分割文件的方法,方法非常的简单实用.分享给大家供大家参考.具体如下: 例子1 指定分割文件大小 配置文件 config.ini: 复制代码 代码如下: [global] #原文件存放目录 dir1=F:\work\python\3595\pyserver\test #新文件存放目录 dir2=F:\work\python\3595\pyserver\test1 python 代码如下: 复制代码 代码如下: #!/usr/bin/python # -*

  • 浅析Python中的多进程与多线程的使用

    在批评Python的讨论中,常常说起Python多线程是多么的难用.还有人对 global interpreter lock(也被亲切的称为"GIL")指指点点,说它阻碍了Python的多线程程序同时运行.因此,如果你是从其他语言(比如C++或Java)转过来的话,Python线程模块并不会像你想象的那样去运行.必须要说明的是,我们还是可以用Python写出能并发或并行的代码,并且能带来性能的显著提升,只要你能顾及到一些事情.如果你还没看过的话,我建议你看看Eqbal Quran的文章

  • 用python分割TXT文件成4K的TXT文件

    复制代码 代码如下: ########################## # # # 为了避免截断中文字符 # # 文件要求是 unicode 编码 # # txt文件另存为对话框下面有下拉框,可选存 # # 储编码格式 # # # ########################## import os import struct filename = str(raw_input("Please enter an old file name: ")) filenamepre = s

随机推荐