什么是多线程
多线程是在单个进程中实现并行性的一种方法,能够执行同时进行的任务。
在单个进程内可以创建多个线程,并在该进程内并行执行较小的任务。
单个进程中的线程共享一个公共内存空间,但它们的堆栈跟踪和寄存器是独立的。
由于共享内存,它们的计算成本较低。
Python中的多线程主要用于执行I/O操作,即如果程序的某个部分正在执行I/O操作,则其余程序可以保持响应。
然而,在Python的实现中,由于全局解释器锁(GIL)的存在,多线程无法实现真正的并行性。
简而言之,GIL是一个互斥锁,一次只允许一个线程与Python字节码交互,即使在多线程模式下,一次也只能有一个线程执行字节码。
这样做是为了在CPython中保持线程安全,但它限制了多线程的性能优势。
多线程实现
现在,我们使用Python实现一个基本的多线程示例。
Python有一个内置的threading
模块用于多线程实现。
1、导入库:
import threading
import os
2、计算平方的函数:
这是一个用于计算数字平方的简单函数,它接受一个数字列表作为输入,并输出列表中每个数字的平方,同时输出使用的线程名称和与该线程关联的进程ID。
def calculate_squares(numbers):
for num in numbers:
square = num * num
print(
f"Square of the number {num} is {square} | Thread Name {threading.current_thread().name} | PID of the process {os.getpid()}"
)
3、主函数:
本示例有一个数字列表,将其平均分成两半,并分别命名为first_half
和second_half
。现在,将为这些列表分配两个独立的线程t1
和t2
。
Thread
函数创建一个新线程,该线程接受一个带有参数列表的函数作为输入。还可以为线程分配一个单独的名称。
.start()
函数将开始执行这些线程,而.join()
函数将阻塞主线程的执行,直到给定的线程完全执行完毕。
if __name__ == "__main__":
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
half = len(numbers) // 2
first_half = numbers[:half]
second_half = numbers[half:]
t1 = threading.Thread(target=calculate_squares, name="t1", args=(first_half,))
t2 = threading.Thread(target=calculate_squares, name="t2", args=(second_half,))
t1.start()
t2.start()
t1.join()
t2.join()
输出:
Square of the number 1 is 1 | Thread Name t1 | PID of the process 345
Square of the number 2 is 4 | Thread Name t1 | PID of the process 345
Square of the number 5 is 25 | Thread Name t2 | PID of the process 345
Square of the number 3 is 9 | Thread Name t1 | PID of the process 345
Square of the number 6 is 36 | Thread Name t2 | PID of the process 345
Square of the number 4 is 16 | Thread Name t1 | PID of the process 345
Square of the number 7 is 49 | Thread Name t2 | PID of the process 345
Square of the number 8 is 64 | Thread Name t2 | PID of the process 345
注意:上述创建的所有线程都是非守护线程。要创建守护线程,需要编写
t1.setDaemon(True)
,将线程t1
设置为守护线程。
现在来了解一下上述代码生成的输出结果。可以观察到两个线程的进程ID(即PID)保持不变,这意味着这两个线程属于同一个进程。
还可以观察到输出并非按顺序生成。第一行中可以看到是线程1生成的输出,然后在第三行是线程2生成的输出,接着在第四行,再次是线程1生成的输出。这清楚地表明这些线程是同时工作的。
并发并不意味着这两个线程并行执行,因为一次只有一个线程被执行。它不会减少执行时间,与顺序执行所需的时间相同。CPU开始执行一个线程,但在中途离开,并切换到另一个线程,过一段时间后,又回到主线程,并从上次离开的地方开始执行。