python 的多线程是通过 threading 模块的 Thread 类来实现的。
- 创建线程对象
t1 = threading.Thread(target=method, args=('jump',))
- 启动线程
t1.start()
我们通过下面的代码看来一下:
import threading
import time
def thread1(name):
print('你好, %s, %s' % (name, time.ctime()))
time.sleep(2)
print('结束, %s, %s' % (name, time.ctime()))
def thread2(name):
print('你好, %s, %s' % (name, time.ctime()))
time.sleep(3)
print('结束, %s, %s' % (name, time.ctime()))
if __name__ == '__main__':
t1 = threading.Thread(target=thread1, args=('thread1',))
t1.start()
t2 = threading.Thread(target=thread1, args=('thread2',))
t2.start()
print('程序结束:%s' % time.ctime())
执行结果:
image.png
我们可以发现,主线程的 print 并不是等 t1、t2 线程都执行完之后再打印的,这是因为主线程和 t1、t2 线程是同时跑的。但是主进程要等非守护子线程结束之后,主线程才会退出。
join
- 上面是 python 多线程的最简单的用法,但是在更多的时候,我们希望主线程的 print 打印在最后面,这样我们知道上面的 t1、t2 线程都结束了。通过下面的例子我们看一下,通过 join 实现我们想要的功能:
if __name__ == '__main__':
t1 = threading.Thread(target=thread1, args=('thread1',))
t1.start()
t2 = threading.Thread(target=thread1, args=('thread2',))
t2.start()
t1.join() #join等t1子线程结束
t2.join() #join等t2子线程结束
print('程序结束:%s' % time.ctime())
执行结果:
image.png
我们可以看到,主线程 print 最后才执行的。主线程执行了打印操作和主线程结束不是一个概念,如果子线程不加 join,则主线程也会执行打印,但是主线程不会结束,还是需要待非守护子线程结束之后,主线程才结束。
守护线程
- 上面我们提到主线程需要等待非守护子线程结束之后,主线程才结束。那么什么是 ‘非守护子线程’ ? 默认的子线程都是主线程的非守护子线程,但是有时候我们有需求,当主进程结束,不管子线程有没有结束,子线程都要跟随主线程一起退出,这时候我们引入一个“守护线程”的概念。
- 如果某个子线程设置为守护线程,主线程其实就不用管这个子线程了,当所有其他非守护线程结束,主线程就会退出,而守护线程将和主线程一起退出,守护主线程,这就是守护线程的意思。
t1 为守护进程:
if __name__ == '__main__':
t1 = threading.Thread(target=thread1, args=('thread1',))
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=thread1, args=('thread2',))
t2.start()
print('程序结束:%s' % time.ctime())
执行结果:
image.png
- t1 为守护进程,主线程不管 t1 的运行状态,只管等待 t2 线程结束,由于 t1 线程的睡眠时间小于 t2 的睡眠时间,所以主线程在等待 t2 线程结束的时候,t1 线程自己结束。
t2 为守护进程:
if __name__ == '__main__':
t1 = threading.Thread(target=thread1, args=('thread1',))
t1.start()
t2 = threading.Thread(target=thread1, args=('thread2',))
t2.setDaemon(True)
t2.start()
print('程序结束:%s' % time.ctime())
执行结果:
image.png
- t2 为守护进程,主线程不管 t2 的运行状态,只管等待 t1 线程结束,由于 t2 线程的睡眠时间大于 t1 的睡眠时间,所以主线程在等待 t1 线程结束的时候,t2 线程未结束。
线程方法
-
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
-
setDaemon(True):将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用 setDaemon 方法。 -
run(): 线程被cpu调度后自动执行线程对象的run方法
-
start():启动线程活动。
-
isAlive(): 返回线程是否活动的。
-
getName(): 返回线程名。
-
setName(): 设置线程名。
threading模块提供的一些方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
python 中的 ‘多线程’ - GIL
我们注意到两如果个任务如果顺序执行要 5s 结束,如果是多线程执行 3S 结束,性能是有所提升的,但是我们要知道这里的性能提升实际上是由于 cpu 并发实现性能提升,也就是 cpu 线程切换(多道技术)带来的,而并不是真正的多cpu并行执行。
- 并发:是指一个系统具有处理多个任务的能力(cpu切换,多道技术)
- 并行:是指一个系统具有同时处理多个任务的能力(cpu同时处理多个任务)
并行是并发的一种情况,子集。
为什么 python 在多线程中为什么不能实现真正的并行操作呢?就是在多 cpu 中执行不同的线程, java 中多个线程可以在不同的 cpu 中,实现并行运行。
-
GIL : 全局解释器锁。无论你启多少个线程,你有多少个 cpu, Python在执行的时候只会的在同一时刻只允许一个线程(线程之间有竞争)拿到 GIL 在一个cpu上运行,当线程遇到 IO 等待或到达者轮询时间的时候,cpu 会做切换,把 cpu 的时间片让给其他线程执行,cpu 切换需要消耗时间和资源,所以计算密集型的功能(比如加减乘除)不适合多线程,因为 cpu 线程切换太多,IO 密集型比较适合多线程。
-
IO密集型(各个线程都会都各种的等待,如果有等待,线程切换是比较适合的),也可以采用可以用多进程+协程
-
计算密集型(线程在计算时没有等待,这时候去切换,就是无用的切换),python不太适合开发这类功能
我们在之前的代码中用了 sleep 操作,其实就是相当于遇到 IO,这种场景用多线程是可以增加性能的,但是如果我们用多线程来计算数据的计算,性能反而会降低。
网友评论