[Python高效编程] - 多线程编程之threading模块

全局解释器锁

Python代码执行由Python虚拟机(解释器主循环)进行执行的。Python在设计时,主循环中同时只能有一个控制线程在执行,就像单核CPU系统中的多线程一样。内存中可以有许多程序,但是在任意时刻只能有一个程序在运行。同理,尽管Python解释器中可以运行多个线程,但是在任意时刻只有一个线程被解释器执行。

对Python虚拟机的访问是由全局解释器(GIL)控制的。这个锁就是用来保证同时只有一个线程运行的。在多线程环境中,Python虚拟机将按照下面所诉的方式执行。

  1. 设置GIL
  2. 切换进一个线程去运行。
  3. 执行下面操作之一:
    a. 指定数量的字节码指令
    b. 线程主动让出控制权(可以调用time.sleep(0)来完成)
  4. 把线程设置回睡眠状态(切换出线程)
  5. 解锁GIL
  6. 重复上述步骤

当调用外部代码(即任意 C/C++扩展的内置函数)时,GIL会保持锁定,直至函数执行结束。例如,对于任意面向I/O的Python例程(调用内置的操作系统C代码的那种)。GIL会在I/O调用前释放,已允许其他线程在I/O执行的时候运行。而对于那些没有太多I/O操作的代码而言,更倾向于在该线程整个时间片内始终占用CPU(和GIL)。
所以 I/O 密集型的 Python 程序要比计算密集型的代码能够更好地利用多线程环境。

如果对源代码、解释器主循环和GIL感兴趣,可以看看Python/ceval.c 文件。

在Python中使用线程

将使用threading模块中的Thread类来实现多线程编程

threading模块中的Thread类

Thread对象的属性和方法

属性描述
Thread对象数据属性
name线程名
ident线程标识符
daemon布尔标志,表示这个线程是否是守护线程
Thread对象方法
__init__(self, group=None, target=None, name=None,args=(), kwargs=None, *, daemon=None)实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs和daemon标志
start()开始执行该线程
run()定义线程功能的方法(通常重写)
join(timeout=None)直至启动的线程终止之前一直挂起,除非给出了timeout,否则一直阻塞
getName()返回线程名
setName(name)设置线程名
is_alive()布尔标志,表示这个线程是否存活
isDaemon()判断是否为守护线程
setDaemon(daemonic)把线程的守护标志设置为布尔值daemonic

使用Thread类,可以有很多方法创建线程。例如:

  • 创建Thread的实例,传给它一个函数
  • 创建Thread的实例,转给它一个可调用的类实列
  • 派生Thread的子类,并创建子类的实例(推荐使用)

创建Thread的实例,传给它一个函数

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: Thu Nov 16 18:53:10 2017
Start loop 0 at: Thu Nov 16 18:53:10 2017
Start loop 1 at: Thu Nov 16 18:53:10 2017
loop 1 done at: Thu Nov 16 18:53:12 2017
loop 0 done at: Thu Nov 16 18:53:14 2017
all DONE at: Thu Nov 16 18:53:14 2017

创建Thread的实例,转给它一个可调用的类实列

import threading
from time import sleep, ctime

loops = [4, 2]

class ThreadFunc:
    def __init__(self, func, args, name=''):
        self.name = name
        self.func = func
        self.args = args
    def __call__(self):
        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:
        t = threading.Thread(target=ThreadFunc(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()
Starting at: Thu Nov 16 18:58:21 2017
Start loop 0 at: Thu Nov 16 18:58:21 2017
Start loop 1 at: Thu Nov 16 18:58:21 2017
loop 1 done at: Thu Nov 16 18:58:23 2017
loop 0 done at: Thu Nov 16 18:58:25 2017
all DONE at: Thu Nov 16 18:58:25 2017

派生Thread的子类,并创建子类的实例

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 run(self):
       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:
        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()
Starting at: Thu Nov 16 19:10:34 2017
Start loop 0 at: Thu Nov 16 19:10:34 2017
Start loop 1 at: Thu Nov 16 19:10:34 2017
loop 1 done at: Thu Nov 16 19:10:36 2017
loop 0 done at: Thu Nov 16 19:10:38 2017
all DONE at: Thu Nov 16 19:10:38 2017

单线程和多线程执行对比

import threading
from time import sleep, ctime


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 = self.func(*self.args)
       print(self.name, 'finished at:', ctime())

def fib(x):
    sleep(0.005)
    if x < 2: return 1
    return (fib(x-2) + fib(x-1))

def fac(x):
    sleep(0.1)
    if x < 2: return 1
    return (x * fac(x-1))

def sum(x):
    sleep(0.1)
    if x < 2: return 1
    return (x + sum(x-1))

funcs = [fib, fac, sum]
n = 12

def main():
    nfuncs = range(len(funcs))
    print('*** SINGLE THREAD')
    for i in nfuncs:
        print("Starting", funcs[i].__name__, 'at:', ctime())
        print(funcs[i](n))
        print(funcs[i].__name__, 'finished at:', ctime())


    print("\n*** MULTIPLE THREADS")
    threads = []
    for i in nfuncs:
        t = MyThread(funcs[i], (n,), funcs[i].__name__)
        threads.append(t)

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

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

    print('all DONE')

if __name__ == '__main__':
    main()
*** SINGLE THREAD
Starting fib at: Thu Nov 16 19:50:11 2017
233
fib finished at: Thu Nov 16 19:50:14 2017
Starting fac at: Thu Nov 16 19:50:14 2017
479001600
fac finished at: Thu Nov 16 19:50:15 2017
Starting sum at: Thu Nov 16 19:50:15 2017
78
sum finished at: Thu Nov 16 19:50:16 2017

*** MULTIPLE THREADS
Starting fib at: Thu Nov 16 19:50:16 2017
Starting fac at: Thu Nov 16 19:50:16 2017
Starting sum at: Thu Nov 16 19:50:16 2017
sum finished at: Thu Nov 16 19:50:17 2017
fac finished at: Thu Nov 16 19:50:17 2017
fib finished at: Thu Nov 16 19:50:19 2017
233
479001600
78
all DONE

本篇博客内容摘取于:Python核心编程(第3版) Wesley Chun 著

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值