《python核心编程》笔记-18 多线程编程(2)

《python核心编程》笔记-18 多线程编程(2)全局解锁器GIL

全局解锁器GIL

python代码的执行由python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在放在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然python解释器可以”运行”多个线程,但在任意时刻,只有一个线程在解释器中运行。
对python虚拟机的访问由全局解释器锁GIL来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,python虚拟机按以下方式执行:

  1. 设置 GIL
  2. 切换到一个线程去运行
  3. 运行:
    a. 指定数量的字节码指令,或者 b. 线程主动让出控制(可以调用 time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁 GIL
  6. 再次重复以上所有步骤
    在调用外部代码(如 C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有 Python的字节码被运行,所以不会做线程切换)。编写扩展的程序员可以主动解锁 GIL。不过,Python 的开发人员则不用担心在这些情况下你的 Python 代码会被锁住。例如,对所有面向 I/O 的(会调用内建的操作系统 C 代码的)程序来说,GIL 会在这个 I/O 调用之前被释放,以允许其它的线程在这个线程等待 I/O 的时候运行。如果某线程并未使用很多 I/O 操作,它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的 Python 程序比计算密集型的程序更能充分利用多线程环境的好处。
    Python 使用 POSIX 兼容的线程,即 pthreads。
    默认情况下,从源代码编译的(2.0 及以上版本的)Python 以及 Win32 的安装包里,线程支持是打开的。

主线程

通常是UI界面和Main函数
当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,
主线程的重要性体现在两方面:
1)是产生其它子线程的线程,
2)通常它必须最后完成执行比如执行各种关闭动作
子线程:
如果需要再创建线程,那么创建的线程就是则主线程的子线程。

守护线程

threading模块支持守护线程:守护线程一般是一个等待用户请求的服务器,如果客户没有提出请求,它就在那等着,
如果你**设定一个线程为守护线程,就表示你在说这个线程是不重要的,**在进程退出的时候,不用等待这个线程退出。

如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的demon属性。即,在线程开始前滴哦用setDaemon()函数设定线程demon标志为True就表示这个线程“不重要”
如果你想要等待子线程完成再退出,就什么都不用做,或者将demon属性设置为False
新的子线程会继承其父线程的demon标志。

threading模块

threading模块允许程序员创建和管理线程;
Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

threading 模块对象描述

Thread :表示一个线程的执行的对象
Lock :锁原语对象(跟 thread 模块里的锁对象相同)
RLock :可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)。
Condition: 条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。
如,状态的改变或值的改变。
Event :通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,
所有的线程都会被激活。
Semaphore: 为等待锁的线程提供一个类似“等候室”的结构
BoundedSemaphore :与 Semaphore 类似,只是它不允许超过初始值
Timer :与 Thread 相似,只是,它要等待一段时间后才开始运行。

Thread类

使用Thread类,可以有多个方法来创建线程,

  • 创建一个 Thread 的实例,传给它一个函数
  • 创建一个 Thread 的实例,传给它一个可调用的类对象
  • 从 Thread派生出一个子类,创建一个这个子类的实例

Thread 对象的函数描述

start() 开始线程的执行
run() 定义线程的功能的函数(一般会被子类重写)
join(timeout=None) 程序挂起,直到线程结束;如果给了 timeout,则最多阻塞 timeout 秒
getName() 返回线程的名字
setName(name) 设置线程的名字
isAlive() 布尔标志,表示这个线程是否还在运行中
isDaemon() 返回线程的 daemon 标志
setDaemon(daemonic) 把线程的 daemon 标志设为 daemonic(一定要在调用 start()函数前调用)

使用多线程 创建两个“计时循环”,一个睡眠4秒钟,一个睡眠2秒钟

#多线程模块
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:
        threads[i].start()

    for i in nloops:
        threads[i].join()
        
    print("all Done at:",ctime())


if __name__ == '__main__':
    main()

输出结果:

starting at: Wed Nov 20 16:38:55 2019
start loop 0 at: Wed Nov 20 16:38:55 2019
start loop 1 at: Wed Nov 20 16:38:55 2019
loop 1 done at: Wed Nov 20 16:38:57 2019
loop 0 done at: Wed Nov 20 16:38:59 2019
all Done at: Wed Nov 20 16:38:59 2019

版权声明:本文为qq_26126831原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。