【多线程】

文章目录

  • 一、线程与进程的概念:
  • 二、多线程实现
  • 三、线程锁
  • 四、线程数量的设置

一、线程与进程的概念:

简单理解
假设总共有3个孩子需要喂饭,孩子每吃一口饭需要咀嚼消化一下。
多线程方案: 雇佣1个保姆,在喂A孩子吃饭,等到他开始咀嚼的时候,开始喂第二个孩子,等到第二个孩子开始咀嚼的时候,开始喂第三个孩子。
多进程方案: 雇佣3个保姆,3个保姆分别喂3个孩子。

线程与进程的概念

  • 进程是资源的分配和调度的独立单元。进程拥有完整的虚拟地址空间,当发生进程切换时,不同的进程拥有不同的虚拟地址空间。而同一进程的多个线程共享同一地址空间(不同进程之间的线程无法共享)
  • 线程是CPU调度的基本单元,一个进程包含若干线程(至少一个线程)。
  • 线程比进程小,基本上不拥有系统资源。线程的创建和销毁所需要的时间比进程小很多
  • 由于线程之间能够共享地址空间,因此,需要考虑同步和互斥操作
  • 一个线程的意外终止会影响整个进程的正常运行,但是一个进程的意外终止不会影响其他的进程的运行。因此,多进程程序安全性更高。

总之,多进程程序安全性高,进程切换开销大,效率低;多线程程序维护成本高,线程切换开销小,效率高。

并发
指在同一时间段内,有多个任务在交替执行。在多任务操作系统中,多个任务可以在同一时间共享CPU,因此表现出来的是并发执行的状态。这种情况下,多个任务的执行是交替进行的,每个任务都会分配到一定的时间片来执行,直到任务完成或者时间片用完。

并发可以提高系统的吞吐量和资源利用率,因为在同一时间段内,多个任务可以同时执行,从而提高CPU的利用率和系统的响应速度。但是,在并发执行中,由于CPU时间片的切换和任务间的上下文切换等开销,会降低系统的效率,增加系统的负担。此外,如果多个任务之间存在资源争用的情况,还需要采用同步机制来保证资源的正确性。

并行
是指在同一时间段内,有多个任务同时执行。在多处理器系统中,不同的处理器可以同时执行不同的任务,因此表现出来的是并行执行的状态。这种情况下,不同的处理器可以同时执行不同的任务,从而提高系统的性能和效率。

并行可以进一步提高系统的性能和吞吐量,因为不同的任务可以同时执行,而不需要等待其他任务的完成。这可以使得系统的处理能力得到充分的发挥,从而提高系统的响应速度和吞吐量。但是,在并行执行中,如果任务之间存在资源争用的情况,需要采用同步机制来保证资源的正确性和一致性。

并发与并行的区别
多线程在单核CPU上表现为并发,因为虽然多个线程同时执行,但在微观层面上仍然是交替执行的。然而,在多核CPU上,多线程可以并行执行,因为它们可以在不同的核心上同时执行。

多进程则不同,它们通常在多核或多CPU的计算机上并行执行。多进程是实现并行处理的有效方式,因为它们可以将任务分配到不同的CPU核心上,从而实现真正的并行处理。

二、多线程实现

线程的执行,是由CPU进行调度的,一个CPU在同一时刻只会执行一个线程,我们看上去的线程A 和 线程B并发执行。

为了让用户感觉这些任务正在同时进行,操作系统利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。

Python中通过threading模块就可以很好地实现多线程,下面是一个简单的例子

import threading
import time

# 在这里是每一个线程函数
def loop_func(i, sec):
    print("This is {} loop.".format(i))
    logging.warning(f'{i},{sec}')
    logging.info(f'{i},{sec}')
    time.sleep(sec)
    print("The {} loop finished.".format(i))

def main():
    l = [10, 20, 15]  # 每个函数的循环时间,这里可以调整为不同线程的参数池
    ts = []
    for i in range(len(l)):
        # 实例化3个Thread对象,target指定调用的函数,args指定传给loop_func的参数
        thread = threading.Thread(target=loop_func, args=(i, l[i]))
        ts.append(thread)
    for i in range(len(l)):
        ts[i].start()  # 启动线程
    for i in range(len(l)):
        ts[i].join()  # 阻塞线程,使得主线程等到所有的子线程完成后再退出结束

    print("Main Thread Finished")


if __name__ == "__main__":
    start_time = time.time()
    main()
    end_time = time.time()
    print("耗时:{:.2f}秒".format(end_time - start_time))

这里有4个重要的地方:

  1. 线程函数的编写,此处为loop_func
  2. 通过threading.Thread()方法,实例化线程
  3. 通过start()方法,启动线程
  4. 通过.join()方法,阻塞线程,使得主线程等到所有的子线程完成后再退出结束

需要注意的是,多线程的执行顺序是不确定的,线程的启动顺序不一定等于线程的执行顺序。

三、线程锁

线程锁(Tread Lock) 是一种同步机制,用于解决多线程访问共享资源可能出现的并发问题。在多线程环境下,多个线程对共享资源的访问可能导致数据不一致或其他不可预测的结果。线程锁通过对共享资源的访问进行控制,确保每一时刻只有一个线程能够访问共享资源,从而避免并发问题的产生。

线程锁的主要作用是保护共享资源,确保多个线程能够安全地访问共享资源。在多线程环境下,如果没有使用线程锁,可能会出现以下几种并发问题。

  1. 数据竞争(Data Race):多个线程同时读写共享资源,导致共享资源的值不确定或无法预测的结果。数据竞争是一种常见的线程安全问题,使用线程锁可以避免数据竞争的发生。在计算机操作系统中,所谓的I/O就是 输入(Input)和输出(Output),也可以理解为读(Read)和写(Write),针对不同的对象,I/O模式可以划分为磁盘IO模型和网络IO模型。通常磁盘IO是容易出现数据竞争的请求。同时在写入磁盘的
  2. 死锁(DeadLock)、
  3. 饿死(Starvation)等并发问题。
import threading
 
# 共享资源
count = 0
 
# 创建互斥锁
lock = threading.Lock()
 
def increment():
    global count
    # 获取锁
    lock.acquire()
    try:
        # 修改共享资源
        count += 1
    finally:
        # 释放锁
        lock.release()
 
        # 创建多个线程并启动
threads = []
for _ in range(10):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()
 
    # 等待所有线程结束
for thread in threads:
    thread.join()
 
    # 打印结果
print("Count:", count)

四、线程数量的设置

业务中,存在大量的网络IO与磁盘IO,这些流程是十分耗时的。线程在IO处理的时候,CPU就空闲出来,利用率不高,但并不是线程设置越多越好

关于线程数量的设定,该博客讲解地很清晰。

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

举例:服务器CPU核数为4核,一个任务线程cpu耗时为20ms,线程等待(网络IO、磁盘IO)耗时80ms,那最佳线程数目:( 80 + 20 )/20 * 4 = 20。也就是设置20个线程数最佳。

  1. CPU密集型:操作内存处理的业务,一般线程数设置为:CPU核数 + 1 或者 CPU核数*2。核数为4的话,一般设置 5 或 8
  2. IO密集型:文件操作,网络操作,数据库操作,一般线程设置为:cpu核数 / (1-0.9),核数为4的话,一般设置 40

https://blog.csdn.net/Rocky006/article/details/131655179
https://wenku.baidu.com/view/ab6ac3a2ac45b307e87101f69e3143323968f5d7.html
https://zhuanlan.zhihu.com/p/296796074

你可能感兴趣的:(python)