多线程

多线程理论

(1)什么是线程

  • 在 Python 中,线程(Thread)是执行单元的最小单位。线程是进程内的一条执行路径,每个线程都有自己的执行序列、执行环境和栈空间,但它们共享同一个进程的地址空间。

  • 在多线程编程中,可以同时运行多个线程,每个线程执行不同的任务,从而实现并发执行。相比于多进程,线程的开销较小,线程之间的切换更快,因为它们共享进程的内存空间。

  • 在 Python 中,线程可以通过标准库中的 threading 模块来创建和管理。通过创建 Thread 对象并将要执行的函数传递给该对象,就可以创建一个新的线程。Python 提供了丰富的线程控制和同步机制,如互斥锁、信号量等,用于协调和管理多个线程之间的并发访问。

  • 需要注意的是,在 Python 中由于全局解释器锁(GIL)的存在,多线程并不能真正实现多核并行计算,因为在任何给定时刻只有一个线程能够执行 Python 字节码。因此,在 CPU 密集型任务中,多线程并不能显著提高性能,但对于 I/O 密集型任务,多线程可以提高程序的响应速度和并发处理能力。

(2)线程的创建开销

  • 线程的创建开销包括多个方面,主要取决于操作系统和编程语言的实现。在 Python 中,线程的创建开销相对较高,主要原因是因为 Python 的全局解释器锁(Global Interpreter Lock,GIL)以及线程对象的额外开销。

  • 以下是影响线程创建开销的一些因素:

  1. 内核态和用户态切换: 线程是由操作系统内核来创建和管理的,因此线程的创建需要进行内核态和用户态之间的切换,这个切换过程会产生一定的开销。
  2. 内存分配和资源管理: 每个线程都需要分配一定的内存空间来存储线程的执行环境、栈空间以及其他相关信息。此外,操作系统需要为每个线程分配一些资源,如线程 ID、线程控制块等。
  3. GIL 的影响: 在 Python 中,由于全局解释器锁(GIL)的存在,多个线程不能同时执行 Python 字节码,因此线程的并行性受到限制。线程创建时,由于 GIL 的存在,可能会导致一定的额外开销。
  4. 线程对象的额外开销: 在 Python 中,线程对象(Thread)的创建也会带来一定的开销,包括线程对象本身的内存占用、管理开销等。
  5. 竞争和同步开销: 如果线程需要访问共享资源或进行同步操作,还会引入额外的竞争和同步开销,如锁的获取和释放等。
  • 总的来说,虽然线程的创建开销相对较低,但在 Python 中,由于 GIL 的存在以及额外的线程对象开销等因素,线程的创建开销可能会比较显著。因此,在设计多线程应用程序时,需要谨慎考虑线程的创建数量和资源管理,以避免不必要的开销和性能下降。

(3)线程和进程的区别

线程(Thread)和进程(Process)是操作系统中的两个核心概念,用于实现并发执行任务。它们之间的主要区别在于以下几个方面:

  1. 执行单位:

    • 进程是操作系统中的一个独立执行单元,拥有独立的地址空间、内存、文件描述符等资源。每个进程都有自己的代码段、数据段、堆栈等。
    • 线程是进程内的一个独立执行单元,多个线程共享同一个进程的地址空间和资源,包括内存、文件描述符、打开的文件等。因此,多个线程之间可以共享数据和通信。
  2. 资源消耗:

    • 进程之间的资源是相互独立的,因此进程的创建和销毁比较耗费系统资源,包括内存和 CPU 时间。
    • 线程之间共享相同的资源,因此线程的创建和销毁开销相对较小,但是线程之间的同步和通信会引入额外的开销。
  3. 通信与同步:

    • 进程之间的通信和同步相对复杂,通常需要使用进程间通信(Inter-Process Communication,IPC)机制,如管道、消息队列、共享内存等。
    • 线程之间共享同一个进程的地址空间和资源,因此线程之间的通信和同步相对简单,可以直接通过共享变量等方式进行通信和同步。
  4. 并发性和性能:

    • 由于进程之间拥有独立的资源,因此多进程可以实现真正的并行执行,适合处理 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
posted @ 2024-01-28 17:25  ssrheart  阅读(4)  评论(0编辑  收藏  举报