二 多线程
2.1 单核CPU到多核CPU
- 随着计算机技术的发展,毫无疑问现代计算机的处理速度和计算能力也越来越强,然而细心的同学们可能早已注意到,从2005年起,单核的 CPU 性能就没有显著的提升了(见下图),究其原因,是人们发现单纯的提高单核 CPU 的性能无论从潜力上还是功耗上都是不合算的。
- 随着 Intel 的 NetBurst 架构退出江湖,处理器彻底进入了多核时代,从最初的双核一路飙升到现在的动辄上百核的 CPU,性能的提升不以里计。
- 同时一系列针对特殊计算的 accelerator 的出现,并行硬件的发展现在正是百花齐放。多年以前要用许多台电脑才能并行处理的“大数据”问题,现在大多都可以用一台多核电脑解决了。
2.2 线程与进程
- Thread线程是操作系统能够进行运算调动的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。
例如"在桌面上双击打开一个App", 桌面App程序会调用OS的系统调用接口fork,让OS 创建一个进程出来,OS为你准备好进程的结构体对象,将这个App的文件(xxx.exe, 存放编译好的代码指令)加载到进程的代码段,同时OS会为你创建一个线程(main thread), 在代码里面,还可以调用OS的接口,来创建多个线程,这样OS就可以调度这些线程执行了,接下来我们来来看python 的线程
2.3 多线程
2.3.1 线程的创建
方式一:传递可调用对象给threading.Thread构造函数
你可以将一个可调用对象(通常是函数或方法)传递给threading.Thread的构造函数,线程将执行这个可调用对象。
import threading
# 定义一个可调用函数
def my_function():
# 线程的执行逻辑写在这里
print("线程开始执行")
# 创建线程对象,传递可调用函数
thread2 = threading.Thread(target=my_function)
# 启动线程
thread2.start()
# 等待线程结束
thread2.join()
print("线程执行完毕")
- 案例
# ---encoding:utf-8---
# @Time : 2023/9/11 21:16
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : Thread 线程
# @File : ThreadTest.py
import threading
if __name__ == '__main__':
# 1. 创建线程
# target: 线程执行的目标函数
# args: 以元组的方式给线程传参
# kwargs: 以字典的方式给线程传参
# name: 线程的名字
# daemon: 是否是守护线程
# thread = threading.Thread(target=func, args=(1, 2, 3), kwargs={"a": 1, "b": 2}, name="线程1", daemon=True)
# thread = threading.Thread(target=func, args=(1, 2, 3), kwargs={"a": 1, "b": 2}, name="线程1")
# thread = threading.Thread(target=func, args=(1, 2, 3), kwargs={"a": 1, "b": 2})
# thread = threading.Thread(target=func, args=(1, 2, 3))
# thread = threading.Thread(target=func)
thread = threading.Thread()
# 2. 启动线程
thread.start()
# 3. 等待线程执行完毕
thread.join()
# 4. 获取线程名字
print(thread.name)
# 5. 获取当前线程
print(threading.current_thread())
# 6. 获取所有线程
print(threading.enumerate())
# 7. 获取活跃线程数量
print(threading.active_count())
# 8. 判断线程是否存活
print(thread.is_alive())
方式二:继承threading.Thread类:
你可以创建一个自定义的线程类,继承自threading.Thread,并覆盖其run方法来定义线程的执行逻辑。
# ---encoding:utf-8---
# @Time : 2023/9/11 21:32
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : 继承线程创建
# @File : ThreadTest01.py
import threading
class MyThread(threading.Thread):
def run(self):
# 线程的执行逻辑写在这里
print("线程开始执行")
print(threading.current_thread())
print("线程执行结束")
if __name__ == '__main__':
# 创建线程对象
thread1 = MyThread()
# 启动线程
thread1.start()
# 等待线程结束
thread1.join()
print("线程执行完毕")
- 无论哪种方式,一旦线程对象被创建并启动,它会执行run方法或可调用对象中的代码,并在完成后退出。
- 记得在程序结束前使用join方法等待线程执行完毕,以确保主线程不会在子线程之前退出
2.3.2 线程的状态
- 新建(New):线程被创建但尚未开始执行。这是线程对象被创建后的初始状态。
- 就绪(Runnable):线程已经准备好执行,但尚未获得CPU执行时间。在多线程环境中,多个线程可以处于就绪状态,等待CPU资源。
- 运行(Running):线程正在执行其任务,正在使用CPU资源。
- 阻塞(Blocked):线程被阻塞,无法执行。这可能是因为线程在等待某些资源(如锁或输入/输出操作)完成,或者它被显式地挂起。
- 终止(Terminated):线程已经完成其任务,或者由于某种原因而被终止。一旦线程终止,它不能再次启动。
# ---encoding:utf-8---
# @Time : 2023/9/11 21:38
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : 线程状态
# @File : ThreadStatus.py
import threading
import time
# 定义一个简单的线程函数
def thread_function():
for i in range(5):
print(f"线程正在执行:{i}")
time.sleep(1)
# 创建线程对象
thread = threading.Thread(target=thread_function)
# 启动线程
thread.start()
# 检查线程状态
while True:
if thread.is_alive():
print("线程仍然在运行...")
else:
print("线程已经结束。")
break
# 主线程等待子线程完成
thread.join()
print("主线程结束。")
if __name__ == '__main__':
pass
在这个示例中,我们首先创建了一个名为thread_function的简单线程函数,该函数只是循环打印数字并休眠1秒钟。然后,我们创建了一个线程对象,并使用**start()方法启动线程。在主线程中,我们使用is_alive()方法来检查线程是否仍然在运行,如果线程仍在运行,就会不断打印消息,一旦线程结束,就会退出循环。最后,我们使用join()**方法等待线程完成,然后打印主线程结束的消息。
2.3.3 关键函数
Python线程的状态切换通常是由Python解释器和操作系统的线程调度器自动管理的,但是在编写多线程应用程序时,你可以使用一些关键函数来影响线程的状态切换。以下是一些关键的线程管理函数:
threading.Thread(target, args)
:用于创建一个新的线程对象。target
参数指定线程要执行的函数,args
参数是传递给线程函数的参数。start()
:启动线程。一旦线程启动,它将进入就绪状态并开始执行。join(timeout=None)
:等待线程完成。调用该方法会阻塞当前线程,直到被调用的线程执行完毕。可以使用timeout
参数来设置最长等待时间。is_alive()
:检查线程是否仍然在运行。返回True
表示线程仍在执行,返回False
表示线程已经结束。setDaemon(daemonic)
:将线程设置为守护线程。守护线程是一种特殊的线程,当主线程退出时,它们会被强制结束。join()
和is_alive()
通常用于等待和监视线程的状态切换。例如,在示例中使用了join()
来等待线程完成,并使用is_alive()
来检查线程是否仍在运行。
线程状态的切换和管理通常由Python解释器和操作系统来处理,开发人员主要使用上述函数来与线程进行交互,以控制线程的行为和等待线程完成。
# ---encoding:utf-8---
# @Time : 2023/9/11 21:42
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : 生产者消费者模型
# @File : Prouduct.py
import threading
import time
import random
# 共享的缓冲区
buffer = []
MAX_BUFFER_SIZE = 5
# 生产者函数
def producer():
while True:
item = random.randint(1, 100) # 随机生成一个数据项
if len(buffer) < MAX_BUFFER_SIZE:
buffer.append(item)
print(f"生产者生产了 {item}")
time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间
# 消费者函数
def consumer():
while True:
if len(buffer) > 0:
item = buffer.pop(0)
print(f"消费者消费了 {item}")
time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 主线程等待子线程完成
producer_thread.join()
consumer_thread.join()
if __name__ == '__main__':
pass
- 在这个示例中,我们创建了一个共享的缓冲区buffer,并定义了一个最大缓冲区大小MAX_BUFFER_SIZE。然后,我们创建了一个生产者函数和一个消费者函数,它们分别模拟生产和消费数据的过程。生产者不断生成随机数据项并将其放入缓冲区,而消费者从缓冲区中取出数据项。
- 我们创建了两个线程,一个用于生产者,一个用于消费者,然后启动它们。这两个线程会同时运行,并模拟生产者和消费者的交互过程。
2.3.4 线程同步
线程同步是在多线程编程中用于控制线程之间协同工作的重要机制,主要有以下几个原因:
- 共享数据访问:当多个线程同时访问和修改共享数据时,可能会导致数据不一致或损坏。线程同步机制确保在任何给定时刻只有一个线程可以访问共享数据,从而防止竞态条件(race conditions)和数据一致性问题。
- 避免竞态条件:竞态条件是多个线程在竞争相同资源时出现的问题。例如,两个线程同时尝试向一个共享计数器增加值,如果没有适当的同步,可能会导致计数器的值不正确。线程同步可以防止这种情况发生。
- 确保正确的执行顺序:在某些情况下,你可能需要确保线程按照特定的顺序执行。例如,在生产者-消费者问题中,生产者应该在消费者之前生产数据。使用线程同步可以控制线程的执行顺序。
- 资源管理:线程同步还用于管理共享资源的访问,如文件、网络连接或硬件设备。多个线程尝试同时访问这些资源可能会导致不稳定性或性能问题。
- 避免死锁:线程同步也有助于避免死锁,即多个线程相互等待对方释放资源的情况。通过合理设计同步机制,可以减少死锁的风险。
- 提高程序性能:虽然线程同步会引入一些开销,但它也可以允许多个线程并发执行,从而提高程序的性能。在合适的情况下,多线程可以更有效地利用多核处理器。
线程同步是多线程编程中的重要概念,它涉及到多个线程协调执行,以确保数据的一致性和正确性。以下是一些常见的线程同步机制和Python中的实现方式:
- 锁(Lock):锁是最基本的线程同步机制之一。它允许一个线程在进入关键区域(临界区)时获得锁,而其他线程必须等待锁被释放才能进入。Python中可以使用
threading.Lock
来创建锁对象。
import threading
lock = threading.Lock()
def example_function():
with lock:
# 临界区代码,只有一个线程可以执行此部分
pass
- 案例
# ---encoding:utf-8---
# @Time : 2023/9/11 21:49
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : Lock 锁 生产者消费者模型
# @File : LockTest.py
import threading
import time
import random
# 共享的缓冲区
buffer = []
MAX_BUFFER_SIZE = 5
# 创建锁对象
lock = threading.Lock()
# 生产者函数
def producer():
while True:
item = random.randint(1, 100) # 随机生成一个数据项
lock.acquire() # 获取锁
if len(buffer) < MAX_BUFFER_SIZE:
buffer.append(item)
print(f"{ threading.current_thread()}-生产者生产了 {item}")
lock.release() # 释放锁
time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间
# 消费者函数
def consumer():
while True:
lock.acquire() # 获取锁
if len(buffer) > 0:
item = buffer.pop(0)
print(f"{ threading.current_thread()}-消费者消费了 {item}")
lock.release() # 释放锁
time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 主线程等待子线程完成
producer_thread.join()
consumer_thread.join()
if __name__ == '__main__':
pass
- 条件变量(Condition):条件变量允许线程等待某个条件满足后再继续执行。它通常与锁一起使用,可以使用
threading.Condition
来创建条件变量对象。
import threading
condition = threading.Condition()
def producer():
with condition:
# 生产数据
condition.notify() # 通知消费者数据已准备好
def consumer():
with condition:
while not data_ready:
condition.wait() # 等待生产者通知数据已准备好
# 消费数据
- 案例
# ---encoding:utf-8---
# @Time : 2023/9/11 21:54
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : Condition 条件变量生产者消费者模型
# @File : ConditionTest.py
import threading
import time
import random
# 共享的缓冲区
buffer = []
MAX_BUFFER_SIZE = 5
# 创建条件变量对象
condition = threading.Condition()
# 生产者函数
def producer():
while True:
item = random.randint(1, 100) # 随机生成一个数据项
condition.acquire() # 获取锁
if len(buffer) < MAX_BUFFER_SIZE:
buffer.append(item)
print(f"{ threading.current_thread()}-生产者生产了 {item}")
condition.notify() # 通知消费者线程
condition.release() # 释放锁
time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间
# 消费者函数
def consumer():
while True:
condition.acquire() # 获取锁
if len(buffer) > 0:
item = buffer.pop(0)
print(f"{ threading.current_thread()}-消费者消费了 {item}")
condition.wait() # 等待生产者线程
condition.release() # 释放锁
time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 主线程等待子线程完成
producer_thread.join()
consumer_thread.join()
if __name__ == '__main__':
pass
- 信号量(Semaphore):信号量用于控制同时访问共享资源的线程数量。它可以用来限制并发访问资源的线程数量。
import threading
semaphore = threading.Semaphore(3) # 允许同时访问的线程数量为3
def example_function():
with semaphore:
# 最多有3个线程可以同时执行此部分
pass
- 案例
# ---encoding:utf-8---
# @Time : 2023/9/11 21:57
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : 信号量 Semaphore 生产者消费者模型
# @File : SemaphoreTest.py
import threading
import time
import random
# 共享的缓冲区
buffer = []
MAX_BUFFER_SIZE = 5
# 创建信号量对象
semaphore = threading.Semaphore(1)
# 生产者函数
def producer():
while True:
item = random.randint(1, 100) # 随机生成一个数据项
semaphore.acquire() # 获取信号量
if len(buffer) < MAX_BUFFER_SIZE:
buffer.append(item)
print(f"{ threading.current_thread()}-生产者生产了 {item}")
semaphore.release() # 释放信号量
time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间
# 消费者函数
def consumer():
while True:
semaphore.acquire() # 获取信号量
if len(buffer) > 0:
item = buffer.pop(0)
print(f"{ threading.current_thread()}-消费者消费了 {item}")
semaphore.release() # 释放信号量
time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 主线程等待子线程完成
producer_thread.join()
consumer_thread.join()
if __name__ == '__main__':
pass
- 事件(Event):事件用于线程之间的通信,一个线程等待事件的状态为真,而另一个线程可以设置事件的状态为真。
import threading
event = threading.Event()
def thread1():
event.wait() # 等待事件变为真
# 执行线程1的操作
def thread2():
# 执行线程2的操作
event.set() # 设置事件为真,通知线程1
- 案例
# ---encoding:utf-8---
# @Time : 2023/9/11 21:59
# @Author : Darwin_Bossen
# @Email :3139066125@qq.com
# @Site : Event 事件 生产者消费者模型
# @File : EventTest.py
import threading
import time
import random
# 共享的缓冲区
buffer = []
MAX_BUFFER_SIZE = 5
# 创建事件对象
event = threading.Event()
# 生产者函数
def producer():
while True:
item = random.randint(1, 100) # 随机生成一个数据项
if len(buffer) < MAX_BUFFER_SIZE:
buffer.append(item)
print(f"{ threading.current_thread()}-生产者生产了 {item}")
event.set() # 设置事件
time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间
# 消费者函数
def consumer():
while True:
if len(buffer) > 0:
item = buffer.pop(0)
print(f"{ threading.current_thread()}-消费者消费了 {item}")
event.clear() # 清除事件
event.wait() # 等待事件
time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 主线程等待子线程完成
producer_thread.join()
consumer_thread.join()
if __name__ == '__main__':
pass
搜索