全局解释器锁
Python代码执行由Python虚拟机(解释器主循环)进行执行的。Python在设计时,主循环中同时只能有一个控制线程在执行,就像单核CPU系统中的多线程一样。内存中可以有许多程序,但是在任意时刻只能有一个程序在运行。同理,尽管Python解释器中可以运行多个线程,但是在任意时刻只有一个线程被解释器执行。
对Python虚拟机的访问是由全局解释器(GIL)控制的。这个锁就是用来保证同时只有一个线程运行的。在多线程环境中,Python虚拟机将按照下面所诉的方式执行。
- 设置GIL
- 切换进一个线程去运行。
- 执行下面操作之一:
a. 指定数量的字节码指令
b. 线程主动让出控制权(可以调用time.sleep(0)来完成) - 把线程设置回睡眠状态(切换出线程)
- 解锁GIL
- 重复上述步骤
当调用外部代码(即任意 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 著