真正意义上的多线程是由CPU来控制的,例如如果一个CPU密集型的程序,用C语言写,运行在一个四核处理器上,采用多线程的话最多可以获得4倍的效率提升。但是用Python写的话,效率不会提高,甚至会变慢,因为Python中的多线程是由GIL控制的,GIL的全称是Global Interpreter Lock(全局解释器锁),Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只能由一个线程在解释器中运行。因此Python中的多线程是表面上的多线程(同一时刻只有一个线程),不是真正的多线程。除了程序本身执行外,还多了线程切换所花的时间。因此,Python多线程相对更适合写I/O密集型的程序,真正对效率要求高的CPU密集型程序都用C/C++去写
Python3 线程中常用的两个模块为:
- _thread
- threading(推荐使用)
一、_thread 模块
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:
_thread.start_new_thread ( function, args[, kwargs] )
参数说明:
- function - 线程函数。
- args - 传递给线程函数的参数,他必须是个tuple类型。
- kwargs - 可选参数。
#!/usr/bin/python3
import _thread
import time
# 为线程定义一个函数
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print ("%s: %s" % ( threadName, time.ctime(time.time()) ))
# 创建两个线程
try:
_thread.start_new_thread( print_time, ("Thread-1", 2, ) )
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print ("Error: 无法启动线程")
while 1:
pass
执行以上程序输出结果如下:
Thread-1: Wed Apr 6 11:36:31 2016 Thread-1: Wed Apr 6 11:36:33 2016 Thread-2: Wed Apr 6 11:36:33 2016 Thread-1: Wed Apr 6 11:36:35 2016 Thread-1: Wed Apr 6 11:36:37 2016 Thread-2: Wed Apr 6 11:36:37 2016 Thread-1: Wed Apr 6 11:36:39 2016 Thread-2: Wed Apr 6 11:36:41 2016 Thread-2: Wed Apr 6 11:36:45 2016 Thread-2: Wed Apr 6 11:36:49 2016
二、threading
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,threading 模块同样提供了Thread类来处理线程,其构造函数原型如下:
threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
下面是Thread的参数说明
- group:默认为None(该参数是为了以后实现ThreadGroup类而保留的)
- target:在run方法中调用的可调用对象,即需要开启线程的可调用对象,比如函数或方法。
- name:线程名称,默认为“Thread-N”形式的名称,N为较小的十进制数。
- args:在参数target中传入的可调用对象的参数元组,默认为空元组()。
- kwargs:在参数target中传入的可调用对象的关键字参数字典,默认为空字典{}。
- daemon:默认为None,即继承当前调用者线程(即开启线程的线程,一般就是主线程)的守护模式属性,如果不为None,则无论该线程是否为守护模式,都会被设置为“守护模式”。
Thread类还提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
我们可以使用threading 模块的Thread类直接创建一个线程:
import time
import random
import threading
def func(name):
s = random.randint(1, 5)
print(f'current thread is {name}, sleeping {s}s.')
time.sleep(s)
print(f'thread {name} is over')
if __name__ == '__main__':
for i in range(1, 5):
t = threading.Thread(target=func, args=(i,))
t.start()
print('Main Thread')
也可以可以通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程:
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
= name
self.counter = counter
def run(self):
print ("开始线程:" + )
print_time(, self.counter, 5)
print ("退出线程:" + )
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")
以上程序执行结果如下:
开始线程:Thread-1 开始线程:Thread-2 Thread-1: Wed Apr 6 11:46:46 2016 Thread-1: Wed Apr 6 11:46:47 2016 Thread-2: Wed Apr 6 11:46:47 2016 Thread-1: Wed Apr 6 11:46:48 2016 Thread-1: Wed Apr 6 11:46:49 2016 Thread-2: Wed Apr 6 11:46:49 2016 Thread-1: Wed Apr 6 11:46:50 2016 退出线程:Thread-1 Thread-2: Wed Apr 6 11:46:51 2016 Thread-2: Wed Apr 6 11:46:53 2016 Thread-2: Wed Apr 6 11:46:55 2016 退出线程:Thread-2 退出主线程
线程同步
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。
python线程池的使用
在过去,我们可以使用第三方模块threadpool来创建线程池。但是现在主流的使用线程池的模块是python3中自带的模块concurrent.futures模块中的ThreadPoolExecutor,如果对threadpool感兴趣的小伙伴可以自行搜索相关的信息。
下面主要介绍一下ThreadPoolExecutor的使用方法,创建一个最大容纳数量为3的线程池对象t,通过submit提交执行的函数到线程池中,但线程池中的某个线程(thread 1)执行完成,则把空闲的线程(thread 4)放入到池子中,直到所有线程执行完成则程序结束。:
import time
import random
from concurrent.futures import ThreadPoolExecutor
def func(name):
s = random.randint(1, 5)
print(f'current thread is {name}, sleeping {s}s.')
time.sleep(s)
print(f'thread {name} is over')
if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=3) as t:
for i in range(1, 6):
t.submit(func, i)
执行结果如下:
current thread is 1, sleeping 1s.
current thread is 2, sleeping 1s.
current thread is 3, sleeping 2s.
thread 1 is over
current thread is 4, sleeping 2s.
thread 2 is over
current thread is 5, sleeping 4s.
thread 3 is over
thread 4 is over
thread 5 is over