码迷,mamicode.com
首页 > 编程语言 > 详细

python多线程-thread模块

时间:2016-06-09 00:19:38      阅读:377      评论:0      收藏:0      [点我收藏+]

标签:

  thread 和 threading 模块都能够实现 python 中的多线程,一般而言使用 threading 更加方便,因为 thread 有很多的缺点,例如当主线程结束后,所以子线程都会强制终止掉,没有警告也没有正常的清理工作。所以一般情况下更推荐使用 threading 模块。不过出于学习的目的,我们两个模块都来看一下。

  在进行代码学习之前,我们要先来了解 python 的 GIL,也就是全局解释器锁。这个锁保证了同一时刻只能有一个线程运行。

  等等……我明明要使用多线程,为什么这个锁却保证只有一个线程运行,这样岂不是无法并发了吗?这岂不是坑爹吗?

  其实,我们知道 python 是一般解释性的语言,也就是我们的代码要经过解释器解释后才能运行,而一个python的进程只有一个python解释器。我们知道 cpu 对线程的调度是无序的,随机的,所以我们无法保证代码的执行顺序。例如我在前面声明了一个变量,后面使用这个变量,但是在 cpu 进行调度的时候,我们后面的代码先运行了,而此时我们的变量却没有声明,这样肯定会导致各种 BUG。GIL 的存在就是为了保证代码的执行不会混乱。在多线程环境中,Python 虚拟机按以下方式执行:

  1.设置 GIL

  2.切换到一个线程去运行

  3.运行:

    a. 指定数量的字节码指令,或者

    b. 线程主动让出控制(可以调用 time.sleep(0))

  4.把线程设置为睡眠状态

  5.解锁 GIL

  6.再次重复以上所有步骤

  也就是说我将代码分块了,将有关系的代码放在一块,这样有依赖性的代码就能一起执行了,而没有关系的代码就可以分开执行了。不过总体而言,还是只有一个解释器在运行,能同时运行的代码也只有一块,只不过做了分离。

  所以也有很多的人说 python 的多线程就是鸡肋,作用不是太大。但是也不能完全这样说。

  例如,对所有面向 I/O 的(会调用内建的操作系统 C 代码的)程序来说,GIL 会在这个 I/O 调用之前被释放,以允许其它的线程在这个线程等待 I/O 的时候运行。如果某线程并未使用很多 I/O 操作,它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的 Python 程序比计算密集型的程序更能充分利用多线程环境的好处。

  在进行文件读写操作,或者爬虫在等待网页下载的时候,就比较适合使用多线程了。

  在讲完 python 是如何实现多线程和上面时候使用多线程之后,下面就来正式开始模块的学习。

 


 

thread 模块

  安装我之前的讨论,在学习任何python内容是,都先调用 help() 函数,查看其内置的帮助文档。帮助文档中有很多的内容,我们先来看看 FUNCTIONS 部分。

1. allocate()allocate_lock() 

  我们可以看到其说明是一样的。 allocate_lock() -> lock object 

  作用都是创建一个 lock object。而详细内容需要看 LockType 部分,现在先放一边。

  同时还要这样的一句话:allocate() is an obsolete synonym,也就是说前面的方法是一个过去的语法,是过时的,所以我们使用allocate_lock()就好。

 

2. exit()exit_thread() 

  exit_thread() is an obsolete synonym,同样的,我们使用 exit()就好。

  和 raise SystemExit 同义,将退出一个线程,除非进行了异常捕获。

  同时,这里也来说说 python中线程退出的方式:

  1.调用 thread.exit()之类的退出函数

  2.使用python退出进程的标准方法,sys.exit(),连进程都退出了,线程也就消失了。

  3.抛出一个 SystemExit 异常

  当然,我们有方法可以进行守护线程的设置,不过这个是 threading 模块中的内容。

 

3. get_ident() -> integer 

  返回一个非零的整数,惟一地标识当前线程和其他线程同时存在。

  这可以用来识别每个线程资源。在一些平台上分配线程的身份是用从1开始的连续数字,但这并不是绝对的,一个线程的身份可能在退出后被另一个线程重用。

 

4. interrupt_main() 

  在主线程中抛出 KeyboardInterrupt 异常。

  子线程可以使用这个方法来中断主线程。

 

5. stack_size([size]) -> size 

  返回创建新线程时使用的线程堆栈的大小。

  可以使用 size 参数来指定堆栈大小(以字节为单位),然后再创建相应的线程。而且必须是0(使用平台或配置默认)或一个正整数,其值至少32768(32 k)。如果不支持更改线程堆栈大小,则触发 ThreadError 异常。如果指定的大小是无效的,将触发 ValueError,同时不修改其大小。32 k字节是目前支持最小的堆栈大小值,其目的是为了保证有足够的堆栈空间翻译本身。注意,一些平台可能有特殊的栈大小限制,例如要求最小堆大于32 kb或要求是系统内存页大小的倍数,详情查看该系统平台是说明文档。通常每页 4 kb 是最常见的,在没有说明文档的时候可以尝试使用 4kb 的倍数。

  一般用不到,方法实在太底层。

  

6. start_new(function, args[, kwargs])/start_new_thread(function, args[, kwargs]) 

  start_new() is an obsolete synonym,所以使用 start_new_thread 就行了。

  开始一个新的线程,并返回其标识符。线程将调用相应的函数 function 并将 args 元祖中的元素作为位置参数,字典 kwargs 作为关键字参数传入。当函数 return 的时候,线程结束,但是其返还值是忽略的。当函数内部触发未处理的异常时,线程将结束,同时打印堆栈跟踪信息,当然触发 SystemExit 就无痕退出了。

  要求一定要有前两个参数。所以,就算我们想要运行的函数不要参数,我们也要传一个空的元组。

 

  可以看出,这里是将一个函数作为一个整体,也就是一个代码块来执行。我们需要做的,就是将我们的业务逻辑封装到一个函数里面去,而函数里面又可以调用其他函数,创建类的实例等等。

  用一张图来总结的话:

技术分享

 

从某处抠来的代码示例:

def loop1():
    print 子线程1开始:, ctime()
    sleep(4)
    print 子线程1结束:, ctime()

def loop2():
    print 子线程2开始:, ctime()
    sleep(2)
    print 子线程2结束:, ctime()

def main():
    print 主线程开始:, ctime()
    thread.start_new_thread(loop1, ())
    thread.start_new_thread(loop2, ())
    sleep(6)        #为了防止主线程停止,而特意等待了6秒,是函数两个函数执行时间的和:4+2=6
    print 主线程结束:, ctime()

if __name__ == __main__:
    main()

 

输出:

技术分享

  可以看出确实达到了多线程的结果,但是我们为了防止主线程的退出而加上了 sleep(6) ,如何不加上的话,结果是这样的:

技术分享

  主线程并没有等待子线程的执行,而主线程执行完毕后,进程退出,子线程也就消失了。为了防止这样情况,我们让主线程等待着,但是,这里因为我知道两个线程执行的时间总和不会超过6秒,事实上,按照用时最长的函数来算,大于4秒就足够了,但是,当我们改成 sleep(4) 后,结果是这样的:

技术分享

  成功执行完毕。

技术分享

  主线程先退出。

  也就是说时间卡的太紧,成功几率还不一定,这要取决于 cpu 是先调度子线程还是主线程,但 cpu 的调度顺序又是不可知的。

  但是,通常情况下我们要执行的函数需要的时间是无法确定的,而且更具 cpu 的性能不同,所需要的时间也就不同,这个时候应该怎么办呢?

  使用线程锁,也就是使用 allocate_lock()方法。这个方法返回一个 LockType 类型锁对象。下面我们来学习 LockType 又是什么东西。

LockType

  其帮助文档依然在 thread 模块中可以看到,去除几个内置方法和重复的方法后,实际上只有3个方法可用,下面我们来看一下。

1.  acquire([wait]) -> bool 

  将代码块加锁。加锁后若线程运行该代码块时,若不进行解锁操作,将阻塞该线程。

  当没有参数的时候,会上加锁,等待另一个线程解锁,如果已经锁定了,则返回True。

  当给定参数是,只有当参数的布尔值为真才会加锁,同时返回一个数值反应是否成功加锁。

  阻塞是不可中断的。

 

2. release() 

  释放锁,让另一个被阻塞的线程获得线程锁。锁对象必须在锁定状态,但是它不必被同一个线程解锁。

 

3. locked() -> bool 

  返回布尔值,判断锁对象是否在锁定状态。

 

某处抠来的代码示例:

 

loops = [4, 2]

def loop(nloop, nsec, lock):
    print 子线程, nloop, 开始:, ctime()
    sleep(nsec)
    print 子线程, nloop, 结束:, ctime()
    lock.release()  # 解锁

def main():
    print 主线程开始:, 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 主线程结束:, ctime()

if __name__ == __main__:
    main()

 

技术分享

  因为两个线程是集合同时开始的,所以开始时打印的那一行显得有点凌乱。

 


  关于 threading 的坑,下篇再填。

 

python多线程-thread模块

标签:

原文地址:http://www.cnblogs.com/scolia/p/5571257.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!