目录

前言

一、为什么使用多线程

二、多线程完成多任务

1.多线程完成多任务

3.设置主线程结束子线程也结束的方法:守护线程

三、进程与线程的对比

1、关系对比       

2、区别对比

3、优缺点对比

四、锁

总结


前言

        单线程串行执行会导致程序效率低下,无法同时处理多项任务。多线程编程可实现并发执行,充分利用计算机资源,节省系统开销,是Python中提升程序性能的关键技术,本文将梳理其核心知识点。


一、为什么使用多线程

        进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要 打开两个QQ软件一样是比较浪费资源的 。

        线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也 就说进程是线程的容器 , ⼀个进程中最少有一个线程来负责执行程序 。同时线程自己不拥有系统 资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有 的全部资源 。这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源。

二、多线程完成多任务

1.多线程完成多任务

① 导⼊线程模块

        import threading
② 通过线程类创建线程对象

线程对象 = threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
                target 执行的目标任务名,这里指的是函数名(方法名),不能加括号
                name 进程名,一般不用设置
                group 进程组,目前只能使tongNone
                args    传递给 target 任务函数的位置参数,必须是元组类型(即使只有一个参数,也要加逗号,如 (10,))
                kwargs    传递给 target 任务函数的关键字参数,必须是字典类型(如 {'a': 1, 'b': 2})

③ 启动线程执行任务

        线程对象.start()

2.线程创建与启动代码

单线程案例:

import time
def music():
    for i in range(3):
        print('听⾳乐...')
    time.sleep(0.2)
def coding():
    for i in range(3):
        print('敲代码...')
    time.sleep(0.2)
if __name__ == '__main__':
    music()
    coding()

多线程案例:

# 导⼊time模块和threading模块
import time
import threading
# 定义⼀个播放⾳乐的函数
def music():
# 循环三次,每次打印听⾳乐的状态并暂停0.2秒
    for i in range(3):
        print('听⾳乐...')
    time.sleep(0.2)
# 定义⼀个编码的函数
def coding():
# 循环三次,每次打印敲代码的状态并暂停0.2秒
    for i in range(3):
        print('敲代码...')
    time.sleep(0.2)
# 程序⼊⼝
if __name__ == '__main__':
    # 创建⼀个线程,⽤于播放⾳乐。通过threading.Thread类创建线程,music函数作为线程要执⾏的⽬标。
    music_thread = threading.Thread(target=music)
    # 创建⼀个线程,⽤于编码⼯作。同样使⽤threading.Thread类创建,coding函数作为线程要执⾏的⽬标。
    coding_thread = threading.Thread(target=coding)
    # 启动⾳乐线程和编码线程
    music_thread.start()
    coding_thread.start()
    # 等待⾳乐线程完成其任务
    music_thread.join()
    # 等待编码线程完成其任务
    coding_thread.join()
    print('程序结束')

3.设置主线程结束子线程也结束的方法:守护线程

示例:

import time
import threading


def work():
    """
    执⾏⼯作任务的函数。
    通过循环模拟⼯作任务的执⾏。每次循环打印⼯作信息并暂停0.2秒,
    以模拟实际⼯作耗时。
    """
    for i in range(10):
        print('work...')
    time.sleep(0.2)


if __name__ == '__main__':
    # 创建⼦线程并设置守护主线程
    # 这⾥的守护线程意味着当主线程结束时,⼦线程会⾃动结束,不需要显式等待⼦线程的结束
    work_thread = threading.Thread(target=work, daemon=True)
    # 启动线程
    work_thread.start()

    # 延时1s
    # 这⾥可能是为了模拟⼀些操作需要时间,或者给⼦线程⼀些时间来完成它的任务
    time.sleep(1)
    print('主线程执⾏完毕')

三、进程与线程的对比

特性 进程 线程
定义 操作系统分配资源的基本单位 进程内的执行单元,共享进程资源
资源开销 高(独立生存、资源) 低(共享进程内存)
启动速度
并发性 多核并行(绕过GIL) 伪并行(受GIL限制)
数据共享 需IPC(队列、管道等) 直接共享内存(需同步机制)
适用场景 CPU密集型任务(计算、多核利用) I/O密集型任务(网络、文件操作)
模块 multiprocessing threading
容错性 高(进程崩溃不影响其他进程) 低(线程崩溃可能导致进程终止)
调试难度 较高(需处理IPC) 较低(共享数据需同步)

1、关系对比       

        ① 线程是依附在进程里面的,没有进程就没有线程。
        ② 一个进程默认提供一条线程,进程可以创建多个线程。

2、区别对比

        ① 进程之间不共享全局变量
        ② 线程之间共享全局变量
        ③ 创建进程的资源开销要比创建线程的资源开销要大
        ④ 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

3、优缺点对比

        ① 进程优缺点:
        优点:可以用多核
        缺点:资源开销大
        ② 线程优缺点
        优点:资源开销小
        缺点:不能使用多核

四、锁

        无论是进程还是线程,当涉及到共享资源时,都需要使用锁来同步访问,以避免竞态条件。Python提供 了 Lock 对象来实现这一功能。

  • 使用线程锁
import threading

# 创建⼀个锁对象,⽤于在多线程中同步对共享资源的访问
lock = threading.Lock()

# 定义线程函数,该函数将计数器增加n次
def thread_function(n):
    global counter
    for _ in range(n):
        # 打印当前线程将计数器增加到的值
        print(f'线程 {threading.current_thread().name} 将计数器增加到 {counter}')
        # 获取锁,确保同⼀时间只有⼀个线程在修改counter
        lock.acquire()
        try:
            # 尝试增加计数器
            counter += 1
        finally:
            # 释放锁,允许其他线程进⾏操作
            lock.release()

# 初始化全局计数器
counter = 0

# 初始化线程列表
threads = []

# 创建并启动5个线程,每个线程都会执⾏thread_function
for i in range(5):
    # 创建线程
    t = threading.Thread(target=thread_function, args=(i,))
    # 将线程添加到线程列表中
    threads.append(t)
    # 启动线程
    t.start()

# 等待所有线程完成执⾏
for t in threads:
    t.join()

# 打印最终的计数器值
print(f'最终的计数器值: {counter}')

总结

        本文系统梳理了Python多线程编程的核心内容,涵盖多线程的应用价值、实现步骤、守护线程用法,对比了进程与线程的关键差异,并介绍了锁的使用以解决共享资源同步问题,为高效运用多线程提升程序性能提供了清晰指引。

更多推荐