说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。可以访问 Python 多任务 了解背景知识。
线程是现代操作系统上进行并行执行的一个流行的编程方面的抽象概念。当一个程序内有多个线程被叉分出用以执行多个流时,这些线程就会在它们之间共享特定的资源(如,内存地址空间、打开的文件),以使叉分开销最小化,并避免大量高成本的IPC(进程间通信)通道。这些功能让线程在并发执行时成为一个高效的机制。
在Linux中,程序中创建的线程(也称为轻量级进程,LWP)会具有和程序的PID相同的“线程组ID”。然后,各个线程会获得其自身的线程ID(TID)。对于Linux内核调度器而言,线程不过是恰好共享特定资源的标准的进程而已。
Python 中,由于存在 GIL(Global Interpreter Lock),任何线程执行前,必须先获得 GIL 锁。线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。
线程是真正的执行单位,线程由进程创建,一个进程中至少包含一个线程(主线程)。单任务的模型,就是一个进程管理一个线程。threading 是 Python 内置的线程管理模块,我们可以通过这个模块实现多线程。
可以通过以下方法查看线程信息:
import threading
t = threading.current_thread() # 获取当前线程
t.ident # 线程 ID
# 4674557440
t.name # 线程名称
# 'MainThread' 注:主线程
# 返回当前活动的所有线程对象的列表
threading.enumerate()
'''
[<_MainThread(MainThread, started 4598101504)>,
<Thread(Thread-4 (_thread_main), started daemon 123145515909120)>,
<Heartbeat(Thread-5, started daemon 123145532698624)>,
<Thread(Thread-7 (_watch_pipe_fd), started daemon 123145567350784)>,
<ControlThread(Thread-3, started daemon 123145584140288)>,
<ParentPollerUnix(Thread-2, started daemon 123145618255872)>]
'''
threading.active_count()
# 6 当前存活线程数
import os
pid = os.getpid() # 获取当前进程的进程ID
pid
# 21621
创建多个线程:
import threading
def my_thread():
t = threading.current_thread() # 获取当前线程
print(f'{t.ident=}, {t.name=}')
# 创建两个线程,name 定义线程名称
t1 = threading.Thread(target=my_thread, args=(), name='task1')
t2 = threading.Thread(target=my_thread, args=(), name='task2')
t = threading.current_thread() # 获取当前线程
print(f'{t.ident=}, {t.name=}')
t1.start() # 同步 t1.join | 结束 t1.terminate()
t2.start()
'''
t.ident=4674557440, t.name='MainThread'
t.ident=123145584340992, t.name='task1'
t.ident=123145584340992, t.name='task2'
'''
线程是程序级的,用命令行在终端查看:
# 每个进程的所有线程,其中包含 PID
# 但它不显示 threadid
ps -M
程序运行时默认就是在主线程上,需要多个线程的话需要我们创建。
如果想创建多个线程可以:
import threading
import time
def my_thread(num):
t = threading.current_thread() # 获取当前线程
time.sleep(.5)
start = time.time()
n = sum(range(num**num))
print(f'{t.name}, 用时{time.time()-start:.6f}', end='\n')
return n
if __name__ == '__main__':
t = threading.current_thread() # 获取当前线程
print(f'{t.name}', end='\n')
tasks = []
for i in range(10):
t = threading.Thread(target=my_thread, args=(i,), name=f'Thread_{i}')
tasks.append(t)
for t in tasks:
t.start()
'''
MainThread
Thread_0, 用时0.000081
Thread_2, 用时0.000016
Thread_3, 用时0.000017
...
Thread_6, 用时0.001033
Thread_9, 用时5.669538
'''
上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?
调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程,这就相当于串行了。
import threading
import time
def my_thread(num):
t = threading.current_thread() # 获取当前线程
time.sleep(.5)
start = time.time()
n = sum(range(num**num))
print(f'{t.name}, 用时{time.time()-start:.6f}', end='\n')
return n
if __name__ == '__main__':
t = threading.current_thread() # 获取当前线程
print(f'{t.name}', end='\n')
tasks = []
for i in range(10):
t = threading.Thread(target=my_thread, args=(i,), name=f'Thread_{i}')
tasks.append(t)
for t in tasks:
t.start()
print(f'\t{t.name} is_alive:{t.is_alive()}')
t.join()
'''
MainThread
Thread_0 is_alive:True
Thread_0, 用时0.000063
Thread_1 is_alive:True
Thread_1, 用时0.000035
Thread_2 is_alive:True
Thread_2, 用时0.000036
...
Thread_8, 用时0.249557
Thread_9 is_alive:True
Thread_9, 用时5.386386
'''
如果想实现所有子线程代码「同时」执行完毕后,再执行后续代码,可以在所有 t.start() 执行完后再统一 t.join():
print('前续代码')
for t in tasks:
t.start()
print(f'\t{t.name} is_alive:{t.is_alive()}')
for t in tasks:
t.join()
print(f'\t{t.name} is_alive:{t.is_alive()}')
print('后续代码')
如果要让一个 Thread 对象启动,调用它的 start() 方法。Thread 的 is_alive() 方法查询线程是否还在运行:
t.is_alive() # True 主进程还在支行
t1.is_alive() # False
t2.is_alive() # False
thread 代码正常运行结束或者是遇到异常,线程会终止。
总结:
todo:
实现 Python 的线程池有以下方法:
# 线程池,推荐
from concurrent.futures import ThreadPoolExecutor as Pool
# 线程池,以多进程相同 API 实现,方便线程进程线程切换
from multiprocessing.dummy import Pool
可以查看教程中多进程的相关介绍。
threading模块中提供了5种最常见的锁,下面是按照功能进行划分:
参考:https://z.itpub.net/article/detail/DE03B9C2CC0ECD3366C7ADCDE9550CE6
同个进程内的线程之间,由于是在同一个内存空间,所以它们之间的数据可以共享。
todo:
在 Python 中多进程可以使用:
# 创建线程
import threading
t1 = threading.Thread()
# 线程池,推荐
from concurrent.futures import ThreadPoolExecutor as Pool
# 线程池,以多进程相同 API 实现,方便线程进程线程切换
from multiprocessing.dummy import Pool
更新时间:April 12, 2022, 10:56 p.m. 标签:python 多线程 线程