Python系列(10)- Python 多线程
多线程(Multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的系统包括对称多处理机、多核心处理器、芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作 “线程”(Thread),利用它编程的概念就叫作 “多线程处理”。
多线程是并行化的一种形式,或者是拆分工作以便同时进行处理。线程化的程序将工作拆分到多个软件线程,而不是将大量工作交给单个内核。这些线程由不同的 CPU 内核并行处理,以节省时间。
多线程运行有如下优点:
(1) 使用线程可以把占据长时间的程序中的任务放到后台去处理;
(2) 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度;
(3) 程序的运行速度可能加快;
(4) 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。
线程可以分为:
(1) 内核线程:由操作系统内核创建和撤销。
(2) 用户线程:不需要内核支持而在用户程序中实现的线程。
并行和并发的区别:
并行(Parallelism):是同一时刻,每个线程都在执行。
并发(Concurrency):是同一时刻,只有一个线程执行,然后交替执行。Python 多线程是并发的。
Python 中与多线程相关的 2 个模块:
(1) _thread
(2) threading (推荐使用)
注:thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用 "thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。
1. _thread 模块
_thread 模块提供低级别的接口,用于支持轻量级的多线程开发,也提供了简单的锁机制(也称为 互斥锁 或 二进制信号)。
调用 _thread 模块中的 start_new_thread( )函数来产生新线程。语法格式如下:
_thread.start_new_thread(function, args[, kwargs])
参数说明:
function - 线程函数。
args - 传递给线程函数的参数,他必须是个tuple类型。
kwargs - 可选参数。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import _thread import time def print_time( name, delay): count = 0 while count < 3: time.sleep(delay) count += 1 print ("%s: %s" % ( name, time.ctime(time.time()) )) print("%s 结束" % name) _thread.start_new_thread(print_time, ("Thread-1", 2)) _thread.start_new_thread(print_time, ("Thread-2", 4)) print("同时按下 Ctrl + C 键退出") while True: pass
输出结果如下:
同时按下 Ctrl + C 键退出 Thread-1: Sat Aug 24 13:02:54 2024 Thread-2: Sat Aug 24 13:02:56 2024 Thread-1: Sat Aug 24 13:02:56 2024 Thread-1: Sat Aug 24 13:02:58 2024 Thread-1 结束 Thread-2: Sat Aug 24 13:03:00 2024 Thread-2: Sat Aug 24 13:03:04 2024 Thread-2 结束 Traceback (most recent call last): File "d:/pythonDemo/thread.py", line 23, in <module> pass KeyboardInterrupt
注:两个线程结束后,主程序仍然处于 while True 循环阻塞,需要在控制台按下 Ctrl + C 键才能退出。
显然,while True 循环阻塞不是很好的线程同步方式,我们可以使用 _thread.allocate_lock() 来实现线程同步,修改代码如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import _thread import time def print_time( name, delay, lock): count = 0 while count < 3: time.sleep(delay) count += 1 print ("%s: %s" % ( name, time.ctime(time.time()) )) print("%s 结束" % name) lock.release() lock1 = _thread.allocate_lock() lock1.acquire() _thread.start_new_thread(print_time, ("Thread-1", 2, lock1)) lock2 = _thread.allocate_lock() lock2.acquire() _thread.start_new_thread(print_time, ("Thread-2", 4, lock2)) while lock1.locked() or lock2.locked(): pass
输出结果如下:
Thread-1: Sat Aug 24 17:24:14 2024 Thread-2: Sat Aug 24 17:24:16 2024 Thread-1: Sat Aug 24 17:24:16 2024 Thread-1: Sat Aug 24 17:24:18 2024 Thread-1 结束 Thread-2: Sat Aug 24 17:24:20 2024 Thread-2: Sat Aug 24 17:24:24 2024 Thread-2 结束
注:两个线程结束后,主程序自动退出。
2. threading 模块
threading 模块除了包含 _thread 模块中的所有方法外,还提供如下方法:
(1) threading.Thread(target, args=(), kwargs={}, daemon=None): 创建 Thread 类的实例;
(2) threading.current_thread(): 返回当前的线程变量;
(3) threading.enumerate(): 返回一个包含正在运行的线程的列表。正在运行指线程启动后、结束前,不包括启动前和终止后的线程;
(4) threading.active_count(): 返回正在运行的线程数量,与 len(threading.enumerate()) 有相同的结果;
threading.Thread() 返回的 Thread 实例包含如下方法与属性:
(1) start():开始线程活动(只能执行一次);
(2) run():线程的方法,在线程被 cpu 调度后,就会自动执行这个方法,可以重写;
(3) join():等待,直到线程结束;
(4) setName():给线程设置名字;
(5) getName():获取线程名字;
(6) is_alive():返回线程是否存活( True 或者 False);
(7) daemon: 守护线程,默认是 False;
1) 使用 threading.Thread() 创建线程
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import threading import time def print_time( name, delay): count = 0 while count < 3: time.sleep(delay) count += 1 print ("%s: %s" % ( name, time.ctime(time.time()) )) print("%s finish" % name) thread1 = threading.Thread(target=print_time, args=("Thread-1", 2)) thread2 = threading.Thread(target=print_time, args=("Thread-2", 4)) thread1.start() thread2.start() print('主程序结束')
输出结果如下:
主程序结束 Thread-1: Sat Aug 24 14:05:32 2024 Thread-2: Sat Aug 24 14:05:34 2024 Thread-1: Sat Aug 24 14:05:34 2024 Thread-1: Sat Aug 24 14:05:36 2024 Thread-1 结束 Thread-2: Sat Aug 24 14:05:38 2024 Thread-2: Sat Aug 24 14:05:42 2024 Thread-2 结束
2) 继承 threading.Thread 创建子类线程
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import threading import time class TestThread(threading.Thread): def __init__(self, name, delay): super().__init__() self.name = name self.delay = delay def run(self): print_time(self.name, self.delay) print("%s 结束" % self.name) def print_time( name, delay): count = 0 while count < 3: time.sleep(delay) count += 1 print ("%s: %s" % ( name, time.ctime(time.time()) )) start = time.time() thread1 = TestThread("Thread-1", 2) thread2 = TestThread("Thread-2", 4) thread1.start() thread2.start() # 等待线程都结束 thread1.join() thread2.join() end = time.time() print('主程序结束', int(end-start), '秒')
输出结果如下:
Thread-1: Sat Aug 24 17:40:40 2024 Thread-2: Sat Aug 24 17:40:42 2024 Thread-1: Sat Aug 24 17:40:42 2024 Thread-1: Sat Aug 24 17:40:44 2024 Thread-1 结束 Thread-2: Sat Aug 24 17:40:46 2024 Thread-2: Sat Aug 24 17:40:50 2024 Thread-2 结束 主程序结束 12 秒
注:在主程序里调用线程的 join() 方法,实现多个线程的同步。
3. 线程锁
有些场景我们不希望多个线程同时读写同一个数据,为了保证数据的正确性,需要管理线程对数据读写。
可以使用 threading 模块的 Lock 或 Rlock 线程锁,两者都有 acquire 方法和 release 方法,可以将数据的读写操作放在 acquire 和 release 方法之间。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import threading import time class TestThread(threading.Thread): def __init__(self, name, delay): super().__init__() self.name = name self.delay = delay def run(self): threadLock.acquire() print_time(self.name, self.delay) print("%s 结束" % self.name) threadLock.release() def print_time( name, delay): count = 0 while count < 3: time.sleep(delay) count += 1 print ("%s: %s" % ( name, time.ctime(time.time()) )) threadLock = threading.Lock() start = time.time() thread1 = TestThread("Thread-1", 2) thread2 = TestThread("Thread-2", 4) thread1.start() thread2.start() thread1.join() thread2.join() end = time.time() print('主程序结束', int(end-start), '秒')
输出结果如下:
Thread-1: Sat Aug 24 18:42:01 2024 Thread-1: Sat Aug 24 18:42:03 2024 Thread-1: Sat Aug 24 18:42:05 2024 Thread-1 结束 Thread-2: Sat Aug 24 18:42:09 2024 Thread-2: Sat Aug 24 18:42:13 2024 Thread-2: Sat Aug 24 18:42:17 2024 Thread-2 结束 主程序结束 18 秒
注:使用了线程锁之后,Thread-2 等 Thread-1 结束后才开始操作数据,程序总用时比没使用线程锁时增加了 6 秒。
4. 守护线程
使用 threading 模块创建的线程,有两种类型:守护线程(Daemon thread)和非守护线程(Non-daemon thread)。默认情况下,当主线程结束时,所有非守护线程会继续运行。而守护线程会在主线程结束时自动退出。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import threading import time def print_time( name, delay): while True: time.sleep(delay) print ("%s: %s" % ( name, time.ctime(time.time()) )) thread1 = threading.Thread(target=print_time, args=("Thread-1", 2)) thread2 = threading.Thread(target=print_time, args=("Thread-2", 4)) print('thread1.daemon:', thread1.daemon) print('thread2.daemon:', thread2.daemon) thread1.start() thread2.start() print('主程序结束')
输出结果如下:
thread1.daemon: False thread2.daemon: False 主程序结束 Thread-1: Sat Aug 24 20:58:18 2024 Thread-2: Sat Aug 24 20:58:20 2024 Thread-1: Sat Aug 24 20:58:20 2024 Thread-1: Sat Aug 24 20:58:22 2024 Thread-2: Sat Aug 24 20:58:24 2024 Thread-1: Sat Aug 24 20:58:24 2024 Thread-1: Sat Aug 24 20:58:26 2024 Thread-2: Sat Aug 24 20:58:28 2024 ...
注:当主线程结束时,非守护线程还会继续运行。
我们把 thread1、thread2 都改成守护线程,修改代码如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import threading import time def print_time( name, delay): while True: time.sleep(delay) print ("%s: %s" % ( name, time.ctime(time.time()) )) thread1 = threading.Thread(target=print_time, args=("Thread-1", 2)) thread2 = threading.Thread(target=print_time, args=("Thread-2", 4)) thread1.daemon = True thread2.daemon = True print('thread1.daemon:', thread1.daemon) print('thread2.daemon:', thread2.daemon) thread1.start() thread2.start() print('主程序结束')
输出结果如下:
thread1.daemon: True
thread2.daemon: True
主程序结束
注:当主线程结束时,守护线程自动退出了。
5. 线程优先级队列(Queue)
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语 (Lock primitive),能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
(1) qsize():返回队列的大小;
(2) empty():如果队列为空,返回 True,反之 False;
(3) full():如果队列满了,返回 True,反之 False;
(4) get([block[, timeout]]):获取队列,timeout 等待时间;
(5) get_nowait():相当于 get(False);
(6) put(item):写入队列;
(7) put_nowait(item):相当于 put(item, False);
(8) task_done():在完成一项工作之后,Queue.task_done() 函数向任务已经完成的队列发送一个信号;
(9) join():实际上意味着等到队列为空,再执行别的操作;
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import queue import threading import time class TestThread(threading.Thread): def __init__(self, name, delay, q): super().__init__() self.name = name self.delay = delay self.q = q def run(self): print_time(self.name, self.delay, self.q) print("%s 结束" % self.name) def print_time( name, delay, q): while q.qsize() > 0: i = q.get() time.sleep(delay) print ("%s: %s" % ( name, time.ctime(time.time()) )) q.task_done() # 任务处理完成,通知队列 start = time.time() # 创建 Queue 任务队列 q = queue.Queue() for i in range(6): q.put(i) print('Queue 队列: ', q.qsize(), '任务') thread1 = TestThread("Thread-1", 2, q) thread2 = TestThread("Thread-2", 4, q) thread1.start() thread2.start() q.join() end = time.time() print('主程序结束', int(end-start), '秒')
输出结果如下:
Queue 队列: 6 任务 Thread-1: Sat Aug 24 21:21:49 2024 Thread-1: Sat Aug 24 21:21:51 2024 Thread-2: Sat Aug 24 21:21:51 2024 Thread-1: Sat Aug 24 21:21:53 2024 Thread-1: Sat Aug 24 21:21:55 2024 Thread-2: Sat Aug 24 21:21:55 2024 Thread-1 结束 Thread-2 结束 主程序结束 8 秒