多线程理论
(1)什么是线程
-
在 Python 中,线程(Thread)是执行单元的最小单位。线程是进程内的一条执行路径,每个线程都有自己的执行序列、执行环境和栈空间,但它们共享同一个进程的地址空间。
-
在多线程编程中,可以同时运行多个线程,每个线程执行不同的任务,从而实现并发执行。相比于多进程,线程的开销较小,线程之间的切换更快,因为它们共享进程的内存空间。
-
在 Python 中,线程可以通过标准库中的
threading
模块来创建和管理。通过创建Thread
对象并将要执行的函数传递给该对象,就可以创建一个新的线程。Python 提供了丰富的线程控制和同步机制,如互斥锁、信号量等,用于协调和管理多个线程之间的并发访问。 -
需要注意的是,在 Python 中由于全局解释器锁(GIL)的存在,多线程并不能真正实现多核并行计算,因为在任何给定时刻只有一个线程能够执行 Python 字节码。因此,在 CPU 密集型任务中,多线程并不能显著提高性能,但对于 I/O 密集型任务,多线程可以提高程序的响应速度和并发处理能力。
(2)线程的创建开销
-
线程的创建开销包括多个方面,主要取决于操作系统和编程语言的实现。在 Python 中,线程的创建开销相对较高,主要原因是因为 Python 的全局解释器锁(Global Interpreter Lock,GIL)以及线程对象的额外开销。
-
以下是影响线程创建开销的一些因素:
- 内核态和用户态切换: 线程是由操作系统内核来创建和管理的,因此线程的创建需要进行内核态和用户态之间的切换,这个切换过程会产生一定的开销。
- 内存分配和资源管理: 每个线程都需要分配一定的内存空间来存储线程的执行环境、栈空间以及其他相关信息。此外,操作系统需要为每个线程分配一些资源,如线程 ID、线程控制块等。
- GIL 的影响: 在 Python 中,由于全局解释器锁(GIL)的存在,多个线程不能同时执行 Python 字节码,因此线程的并行性受到限制。线程创建时,由于 GIL 的存在,可能会导致一定的额外开销。
- 线程对象的额外开销: 在 Python 中,线程对象(Thread)的创建也会带来一定的开销,包括线程对象本身的内存占用、管理开销等。
- 竞争和同步开销: 如果线程需要访问共享资源或进行同步操作,还会引入额外的竞争和同步开销,如锁的获取和释放等。
- 总的来说,虽然线程的创建开销相对较低,但在 Python 中,由于 GIL 的存在以及额外的线程对象开销等因素,线程的创建开销可能会比较显著。因此,在设计多线程应用程序时,需要谨慎考虑线程的创建数量和资源管理,以避免不必要的开销和性能下降。
(3)线程和进程的区别
线程(Thread)和进程(Process)是操作系统中的两个核心概念,用于实现并发执行任务。它们之间的主要区别在于以下几个方面:
-
执行单位:
- 进程是操作系统中的一个独立执行单元,拥有独立的地址空间、内存、文件描述符等资源。每个进程都有自己的代码段、数据段、堆栈等。
- 线程是进程内的一个独立执行单元,多个线程共享同一个进程的地址空间和资源,包括内存、文件描述符、打开的文件等。因此,多个线程之间可以共享数据和通信。
-
资源消耗:
- 进程之间的资源是相互独立的,因此进程的创建和销毁比较耗费系统资源,包括内存和 CPU 时间。
- 线程之间共享相同的资源,因此线程的创建和销毁开销相对较小,但是线程之间的同步和通信会引入额外的开销。
-
通信与同步:
- 进程之间的通信和同步相对复杂,通常需要使用进程间通信(Inter-Process Communication,IPC)机制,如管道、消息队列、共享内存等。
- 线程之间共享同一个进程的地址空间和资源,因此线程之间的通信和同步相对简单,可以直接通过共享变量等方式进行通信和同步。
-
并发性和性能:
- 由于进程之间拥有独立的资源,因此多进程可以实现真正的并行执行,适合处理 CPU 密集型任务。
- 线程共享相同的资源,因此多线程更适合处理 I/O 密集型任务,但由于全局解释器锁(GIL)的存在,Python 中的多线程并不能真正实现多核并行,适合处理 I/O 密集型任务和简单的并发处理。
总的来说,进程是系统中的独立执行单元,拥有独立的资源,适合处理 CPU 密集型任务;而线程是进程内的独立执行单元,共享进程的资源,适合处理 I/O 密集型任务和简单的并发处理。
(4)为何要有多线程
(1)开设进程
- 申请内存空间 -- 耗资源
- 拷贝代码 - 耗资源
(2)开设线程
- 一个进程内可以开设多个线程
- 在一个进程内开设多个线程无需再次申请内存空间及拷贝代码操作
(3)总结线程的优点
- 减少了资源的消耗
- 同一个进程下的多个线程资源共享
(4)什么是多线程
- 多线程指的是
- 在一个进程中开启多个线程
- 简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。
- 多线程共享一个进程的地址空间
- 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
- 若多个线程都是cpu密集型的,那么并不能获得性能上的增强
- 但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
- 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于Python)
多线程开设
(1)threading模块
import time
from threading import Thread
def run(i):
print(f'这是线程{i}')
time.sleep(2)
def main_first():
task_list = []
for i in range(1, 5):
thread = Thread(target=run, args=(i,))
task_list.append(thread)
task_list_new = []
for i in task_list:
i.start()
task_list_new.append(i)
for p in task_list_new:
p.join()
if __name__ == '__main__':
start_time = time.time()
main_first()
print(f'总耗时:>>> {time.time() - start_time}')
# 这是线程1
# 这是线程2
# 这是线程3
# 这是线程4
# 总耗时:>>> 2.012131929397583
(2)继承Thread父类
from threading import Thread
import time
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
# 定义 run 函数
def run(self):
print(f'{self.name} is running')
time.sleep(3)
print(f'{self.name} is ending')
def main():
t = MyThread('heart')
t.start()
print(f'this is a main process')
if __name__ == '__main__':
main()
# heart is running
# this is a main process
# heart is ending
(3)查看pid
- 多个线程其实是开设在一个进程下的,子线程的进程id和主线程是一样的
import time
from multiprocessing import Process
from threading import Thread
import os
def run(i):
print(f'这是参数:>>> {i}')
print(f'这是子的ID:>>> {os.getpid()}')
time.sleep(2)
def main_process():
task_list = [Process(target=run, args=(i,)) for i in range(1, 5)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(f'这是主进程的ID :>>> {os.getpid()}')
def main_thread():
task_list = [Thread(target=run, args=(i,)) for i in range(1, 5)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(f'这是主线程的ID :>>> {os.getpid()}')
if __name__ == '__main__':
main_process()
# 这是参数:>>> 2
# 这是子的ID:>>> 5644
# 这是参数:>>> 1
# 这是子的ID:>>> 1904
# 这是参数:>>> 3
# 这是子的ID:>>> 13532
# 这是参数:>>> 4
# 这是子的ID:>>> 11968
# 这是主进程的ID :>>> 10196
main_thread()
# 多个线程其实是开设在一个进程下的,子线程的进程id和主线程是一样的
# 这是参数:>>> 1
# 这是子的ID:>>> 16416
# 这是参数:>>> 2
# 这是子的ID:>>> 16416
# 这是参数:>>> 3
# 这是子的ID:>>> 16416
# 这是参数:>>> 4
# 这是子的ID:>>> 16416
# 这是主线程的ID :>>> 16416
(4)多线程共享数据
- 同一个进程下的所有线程共享同一个进程的资源
- 多进程是不共享的,每一块内存空间都是独立的
- 多进程的代码和结果如下
import time
from multiprocessing import Process
import os
number = 0
def run(i):
global number
number += 1
print(f'这是run函数内的number:>>> {number}')
print(f'这是参数:>>> {i}')
print(f'这是子的ID:>>> {os.getpid()}')
time.sleep(2)
def main_process():
task_list = [Process(target=run, args=(i,)) for i in range(1, 5)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(f'这是主进程的ID :>>> {os.getpid()}')
if __name__ == '__main__':
main_process()
# 这是run函数内的number:>>> 1
# 这是参数:>>> 3
# 这是子的ID:>>> 15116
# 这是run函数内的number:>>> 1
# 这是参数:>>> 2
# 这是子的ID:>>> 17212
# 这是run函数内的number:>>> 1
# 这是参数:>>> 4
# 这是子的ID:>>> 1756
# 这是run函数内的number:>>> 1
# 这是参数:>>> 1
# 这是子的ID:>>> 6716
# 这是主进程的ID :>>> 15800
- 然而多线程是共享的,代码如下
import time
from threading import Thread
import os
number = 0
def run(i):
global number
number += 1
print(f'这是run函数内的number:>>> {number}')
print(f'这是参数:>>> {i}')
print(f'这是子的ID:>>> {os.getpid()}')
time.sleep(2)
def main_thread():
task_list = [Thread(target=run, args=(i,)) for i in range(1, 5)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(f'这是主线程的ID :>>> {os.getpid()}')
if __name__ == '__main__':
main_thread()
# 这是run函数内的number:>>> 1
# 这是参数:>>> 1
# 这是子的ID:>>> 13888
# 这是run函数内的number:>>> 2
# 这是参数:>>> 2
# 这是子的ID:>>> 13888
# 这是run函数内的number:>>> 3
# 这是参数:>>> 3
# 这是子的ID:>>> 13888
# 这是run函数内的number:>>> 4
# 这是参数:>>> 4
# 这是子的ID:>>> 13888
# 这是主线程的ID :>>> 13888
(5)查看当前进程的名字 current_thread
- current_thread().name
import time
from threading import Thread, current_thread
import os
number = 0
def run(i):
global number
number += 1
print(f'这是run函数内的number:>>> {number}')
print(f'这是参数:>>> {i}')
print(f'这是子的ID:>>> {os.getpid()}')
time.sleep(2)
def main_thread():
task_list = [Thread(target=run, args=(i,)) for i in range(1, 5)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(f'这是主线程的ID :>>> {os.getpid()}')
print(f'这是主线程的名字 :>>> {current_thread().name}')
if __name__ == '__main__':
main_thread()
# 这是run函数内的number:>>> 1
# 这是参数:>>> 1
# 这是子的ID:>>> 10552
# 这是run函数内的number:>>> 2
# 这是参数:>>> 2
# 这是子的ID:>>> 10552
# 这是run函数内的number:>>> 3
# 这是参数:>>> 3
# 这是子的ID:>>> 10552
# 这是run函数内的number:>>> 4
# 这是参数:>>> 4
# 这是子的ID:>>> 10552
# 这是主线程的ID :>>> 10552
# 这是主线程的名字 :>>> MainThread
(6)守护线程 daemon
from threading import Thread
import time
def task(name):
print(f'当前 {name} is beginning')
time.sleep(2)
print(f'当前 {name} is ending')
def main():
t = Thread(target=task, args=('heart',))
# 开启守护线程
t.daemon = True
t.start()
print(f' this is main process')
if __name__ == '__main__':
main()
# 当前 heart is beginning
# this is main process
(7)线程的互斥锁
(1)未加锁
from threading import Thread,Lock
import time
money = 100
def task():
global money
# 模拟获取到车票信息
temp = money
# 模拟网络延迟
time.sleep(2)
# 模拟购票
money = temp - 1
def main():
t_list = []
for i in range(5):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
# 所有子线程结束后打印 money
print(money)
if __name__ == '__main__':
main()
# 99
(2)加锁后
from threading import Thread,Lock
import time
money = 100
lock = Lock()
def task():
global money
lock.acquire()
# 模拟获取到车票信息
temp = money
# 模拟网络延迟
time.sleep(2)
# 模拟购票
money = temp - 1
lock.release()
def main():
t_list = []
for i in range(5):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
# 所有子线程结束后打印 money
print(money)
if __name__ == '__main__':
main()
# 95
标签:__,多线程,run,task,线程,main,ID
From: https://www.cnblogs.com/ssrheart/p/17993058