Python中提供了多个模块来支持多线程编程,包括thread
,threading
,Queue
模块名等。thread
提供了基本的线程和锁定支持;threading
模块提供了更高级别、功能更全面的线程管理;Queue
模块提供了一个数据结构,使得数据可以在多线程之间共享
thread
(python3中为_thread
)
低级模块,不推荐使用
thread
模块相对来说比较低级,除了派生线程,还提供了基本的同步数据结构(lock object
)。
下面是一个例子
import _thread
from time import sleep, ctime
def loop0():
print(f'start loop 0 at {ctime()}')
sleep(4)
print(f'loop 0 done at {ctime()} ')
def loop1():
print(f'start loop 1 at {ctime()}')
sleep(2)
print(f'loop 1 done at {ctime()} ')
def main():
print(f'starting at {ctime()}')
_thread.start_new_thread(loop0,())
_thread.start_new_thread(loop1,())
sleep(6)
print(f'all done at {ctime()}')
if __name__ == "__main__":
main()
上述例子的运行结果为:
starting at Sun Mar 15 13:32:20 2020
start loop 0 at Sun Mar 15 13:32:20 2020
start loop 1 at Sun Mar 15 13:32:20 2020
loop 1 done at Sun Mar 15 13:32:22 2020
loop 0 done at Sun Mar 15 13:32:24 2020
all done at Sun Mar 15 13:32:26 2020
我们可以看到,两个函数loop0
和loop1
(几乎)在同一时启动,并且在其指定的休眠时间后完成(4s,2s),最终两个函数运行时间约为4s。在这里我们需要注意到的一点是,主线程中有一个休眠6s的语句,这是因为在启动子线程之后,主线程是会继续运行的,而当主线程运行完成后,无论子线程是否运行完毕,程序都会退出。
如果想要避免这种主线程不顾子线程死活的情况,则需要上锁
from time import sleep, ctime
import _thread
def loop(nloop, nsec, lock):
print(f"start loop {nloop} at {ctime()}")
sleep(nsec)
print(f"loop {nloop} done at {ctime()} ")
lock.release()
def main():
print(f"starting at {ctime()}")
loops = [4, 2]
locks = []
for i in loops:
lock = _thread.allocate_lock()
lock.acquire()
locks.append(lock)
for i in range(len(loops)):
_thread.start_new_thread(loop, (i, loops[i], locks[i]))
for i in locks:
while i.locked():
pass
print(f"all done at {ctime()}")
if __name__ == "__main__":
main()
我们针对每个线程都请求了一个锁,当线程结束,释放~当所有子线程结束时,所有的锁都已释放,打印最终完成时间
threading
模块
threading
相对于thread
模块来说更为高级,他提供了多种可在多线程中使用的类,其中Thread
类是我们在多线程编程中索要使用到的主要的类。
Thread
类的主要属性有:
属性 | 描述 |
---|---|
name | 线程名 |
ident | 线程标识符 |
daemon | 布尔标志,表示这个线程是守护线程 |
Thread
类的主要方法有:
方法 | 描述 |
---|---|
start() | 开始执行该线程 |
run() | 定义线程功能的方法 |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出timeout,否则会一直阻塞 |
使用Thread
创建线程主要有三种方法:
- 创建
Thread
的实例,传给他一个函数 - 创建
Thread
的实例,传给他一个可调用的类的实例 - 派生
Thread
的子类,并创建子类的实例
常用的为第一和第三中方法
创建Thread
的实例,传给他一个函数
from threading import Thread
from time import sleep, ctime
def loop(nloop, nsec):
print(f'start loop {nloop} at {ctime()}')
sleep(nsec)
print(f'loop {nloop} done at {ctime()}')
def main():
print(f'Starting at {ctime()}')
threads = []
loops = [4, 2]
for i in range(len(loops)):
t = Thread(target=loop, args=(i, loops[i]))
threads.append(t)
for i in threads:
i.start()
for i in threads:
i.join()
print(f'All done at {ctime()}')
if __name__ == "__main__":
main()
上述例子的运行结果为:
Starting at Sun Mar 15 21:09:31 2020
start loop 0 at Sun Mar 15 21:09:31 2020
start loop 1 at Sun Mar 15 21:09:31 2020
loop 1 done at Sun Mar 15 21:09:33 2020
loop 0 done at Sun Mar 15 21:09:35 2020
All done at Sun Mar 15 21:09:35 2020
他使用了更为简单的逻辑来实现了之前的代码相同的功能,不用我们手动的去对锁进行操作。同时,在子线程完全结束之前,主线程是不会退出的。你可以尝试这把join
方法前后的代码注释掉来查看结果
派生Thread
的子类,并创建子类的实例
from threading import Thread
from time import sleep, ctime
class MyThread(Thread):
def __init__(self, func, args, name=''):
super(MyThread, self).__init__()
self.name = name
self.func = func
self.args = args
def get_result(self):
return self.res
def run(self):
print(f'starting {self.name} at {ctime()}')
self.res = self.func(*self.args)
print(f'{self.name} finished at {ctime()}')
def loop(nloop, nsec):
sleep(nsec)
def main():
print(f'Starting at {ctime()}')
threads = []
loops = [4, 2]
for i in range(len(loops)):
t = MyThread(loop, (i, loops[i]), i)
threads.append(t)
for i in threads:
i.start()
for i in threads:
i.join()
print(f'All done at {ctime()}')
if __name__ == "__main__":
main()
这里定义了一个继承自Thread
的子类MyThread
,并且加入了可以获取执行结果的方法get_result