【多线程相关其三】多线程使用

news2025/1/11 17:15:28

1.为什么要使用多线程?
线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄
和其他进程应有的状态。
因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享
内存,从而极大的提升了程序的运行效率。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境
包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程
来实现并发比使用多进程的性能高得要多。
总结起来,使用多线程编程具有如下几个优点:

进程之间不能共享内存,但线程之间共享内存非常容易。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高
python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。

2.如何创建一个多线程?
Python处理线程的模块有两个:thread和threading。Python 3已经停用了thread模块,并改名为_thread模块。Python 3在_thread模块的基础上开发了更高级的threading模块,因此以下的讲解都是基于threading模块。

根据threading底层代码的说明,创建一个线程通常有两种方法:

(1)在实例化一个线程对象时,将要执行的任务函数以参数的形式传入;

(2)继承Thread类的同时重写它的run方法。

在这里插入图片描述

总结起来,使用多线程编程具有如下几个优点:

进程之间不能共享内存,但线程之间共享内存非常容易。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高
python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。

现在我准备创建两个线程,一个线程每隔一秒打印一个“1”,另一个线程每隔2秒打印一个“2”,如何创建并执行呢?两种方法如下:

2.1

import time
import threading

def printNumber(n: int) -> None:
    while True:
        print(n)
        time.sleep(n)

for i in range(1, 3):
    t = threading.Thread(target=printNumber, args=(i, ))
    t.start()

运行结果如下,控制台会不停地、交错地打印“1”和“2”:
在这里插入图片描述

2.2

import time
import threading

class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        # 注意:一定要调用父类的初始化函数,否则否发创建线程
        super().__init__()

    def run(self) -> None:
        while True:
            print(self.n)
            time.sleep(self.n)

for i in range(1, 3):
    t = MyThread(i)
    t.start()

运行结果如下,控制台会不停地、交错地打印“1”和“2”:
在这里插入图片描述

3.主线程和子线程

我们先把上述的代码简单做一下修改,让它在打印的同时打印活跃的线程个数,代码如下

import time
import threading

class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        while True:
            _count = threading.active_count()
            print(self.n, f"当前活跃的线程个数:{_count}")
            time.sleep(self.n)

for i in range(1, 3):
    t = MyThread(i)
    t.start()

在这里插入图片描述

好那么问题来了:当我创建了线程1并开始执行的时候,程序却告诉我有2个活跃的线程呢?同样地,我最终只创建了2个线程,为什么程序却告诉我有3个活跃的线程呢?

让我们回到进程和线程的定义,当我们开始执行这个程序的时候,这个程序成为一个“有生命的”进程,进程至少有一个线程,这个线程就是主线程。当程序执行到第一次t.start()的时候,程序创建了一个子线程,此时活跃的线程个数是2。进一步,当执行第二次t.start()的时候,程序又创建了一个子线程,因此最终活跃的线程个数是3。

注意每个进程只有一个主线程。

  1. 守护线程(Daemon Thread)

守护线程(Daemon Thread)也叫后台进程,它的目的是为其他线程提供服务。如果其他线程被杀死了,那么守护线程也就没有了存在的必要。因此守护线程会随着非守护线程的消亡而消亡。Thread类中,子线程被创建时默认是非守护线程,我们可以通过setDaemon(True)将一个子线程设置为守护线程。

我们把上面这个例子中创建的两个子线程改写为守护线程,看看会发生什么:

import threading
import time


class MyThread(threading.Thread):
    """
    守护线程(Daemon Thread)
    """

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        while True:
            _count = threading.active_count()
            print(self.n, f"当前活跃的线程个数:{_count}")
            time.sleep(self.n)


for i in range(1, 3):
    t = MyThread(i)
    t.daemon = True
    t.start()
print("结束!")

运行结果如下:
在这里插入图片描述

和前面不同,程序打印完“结束!”彻底结束了

和前面完全不同的是:程序打印完“结束!”后就彻底结束了,不再打印任何内容。这是为什么呢?

因为当程序执行完print(“结束!”)以后,主线程就可以结束了,这时候被设定为守护线程的两个子线程会被杀死,然后主线程结束。

现在,如果我把两个子线程的其中一个设置为守护线程,另一个设置为非守护线程,会怎样呢?代码如下:

import time
import threading


class MyThread(threading.Thread):
    """
    将其中一个线程设置为守护线程
    """

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        while True:
            _count = threading.active_count()
            print(self.n, f"当前活跃的线程个数:{_count}")
            time.sleep(self.n)


for i in range(1, 3):
    t = MyThread(i)
    if i == 1:
        t.daemon = True  # 将其中一个线程设置为守护线程
    t.start()
print("结束!")

在这里插入图片描述

你可能会想,守护线程会被杀死,非守护线程继续执行。但实际情况并非如此,结果如下:

两个子线程都在继续执行

这是因为非守护线程作为前台程序还在继续执行,守护线程就还有“守护”的意义,就会继续执行。

需要注意的是:将子线程设置为守护线程必须在调用start()方法之前,否则回引发RuntimeError异常

  1. join()方法

join()会使主线程进入等待状态(阻塞),直到调用join()方法的子线程运行结束。同时你也可以通过设置timeout参数来设定等待的时间,如:

import time
import threading

class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        while True:
            _count = threading.active_count()
            print(f"线程-{self.n}", f"当前活跃的线程个数:{_count}")
            time.sleep(self.n)

for i in range(1, 3):
    t = MyThread(i)
    t.start()
    t.join(3)

执行结果如下:
在这里插入图片描述

  1. 数据安全与线程锁

现在假设你创建了100子线程操作同一个全局变量number,number被初始化为100,所有子线程都对这个number进行-1,子线程同时进行。如果一切正常的话,最终这个number会变成0,然而现实并非如此。代码如下

import time
import threading

number = 100


class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        global number
        tmp = number
        time.sleep(1)
        number = tmp - 1


# 等子线程运行完,再打印number
t_list = []
for i in range(100):
    t = MyThread(i)
    t.start()
    t_list.append(t)

# 主进程等100个线程结束以后,再打印number,确保子进程都对number进行了操作
for t in t_list:
    t.join()

# 确保子线程执行完毕
print("活跃的线程个数:", threading.active_count())
# 输出最终数值
print("number: ", number)

执行结果如下:

在这里插入图片描述

结果并不是0

这种情况称为“脏数据”。产生脏数据的原因是,当一个线程在对数据进行修改时,修改到一半时另一个线程读取了未经修改的数据并进行修改。

如何避免脏数据的产生呢?一个办法就是用join方法,即先让一个线程执行完毕再执行另一个线程。但这样的本质是把多线程变成了单线程,失去了多线程的意义。另一个办法就是用线程锁,threading模块中有如下几种线程锁:

6.1 Lock互斥锁

import time
import threading

number = 100
lock = threading.Lock()  # 实例化一个锁


class MyThread(threading.Thread):
    """
    lock:互斥锁
    """

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        global number
        lock.acquire()  # 上锁,只允许当前线程访问共享的数据
        tmp = number
        time.sleep(0.1)
        number = tmp - 1
        lock.release()  # 释放锁,允许其他线程访问共享数据


t_list = []
for i in range(100):
    t = MyThread(i)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()


# 确保子线程执行完毕
print("活跃的线程个数:", threading.active_count())
# 输出最终数值
print("number: ", number)

执行结果如下:
在这里插入图片描述

输出正常

6.2 Semaphore

BoundedSemaphore类可以设置同一时间更改数据的线程个数

import time
import threading

semaphore = threading.BoundedSemaphore(3)


class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        semaphore.acquire()
        for i in range(100):
            _count = threading.active_count() - 1
            print(f"线程-{self.n}", f"当前活跃的子线程个数:{_count}")
            time.sleep(1)
        semaphore.release()


for i in range(1, 10):
    t = MyThread(i)
    t.start()

执行结果如下:
在这里插入图片描述

Semaphore设置了同时执行的线程的个数

可以看出:虽然活跃的子线程个数有9个,但其实真正执行的子线程个数只有3个

6.3 Event

Event类会在全局定义一个Flag,当Flag=False时,调用wait()方法会阻塞所有线程;而当Flag=True时,调用wait()方法不再阻塞。形象的比喻就是“红绿灯”:在红灯时阻塞所有线程,而在绿灯时又会一次性放行所有排队中的线程。Event类有四个方法:

set():将Flag设置为True
wait():等待
clear():将Flag设置为False
is_set():返回bool值,判断Flag是否为True

Event的一个好处是:可以实现线程间通信,通过一个线程去控制另一个线程。

import time
import threading

event = threading.Event()
event.set()  # 设定Flag = True


class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        if self.n in [3, 4]:
            event.clear()  # 设定Flag = False
            event.wait()  # 线程3和4进入等待

        for i in range(2):
            _count = threading.active_count() - 1
            print(f"线程-{self.n}", f"当前活跃的子线程个数:{_count}")
            time.sleep(2)
            if self.n == 2 and i == 1:
                # 通过线程2来控制线程3和4
                event.set()


for i in range(1, 5):
    t = MyThread(i)
    t.start()

执行结果如下:

在这里插入图片描述

通过一个线程去控制另一个线程

  1. 一些小技巧
    7.1 with上下门管理器

在使用Lock和RLock时,正确的开锁-释放锁非常重要。通过with上下文管理器,可以保证线程锁被正确释放,而且代码也更加简洁。如:

import time
import threading

number = 100
lock = threading.Lock()  # 实例化一个锁


class MyThread(threading.Thread):
    """
    lock:互斥锁
    获取数据之前加上一把锁,保证同一时刻只有一个线程去操作数据
    相当于100个人去抢一个公共厕所,第一个抢到厕所的人,把厕所的门锁起来,这样后面的人就无法在同一时刻去用厕所
    相当于由并发变成了串行,牺牲了效率,保证的数据的安全
    """

    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self) -> None:
        global number
        with lock:
            tmp = number
            # 模拟网络延迟
            time.sleep(0.1)
            number = tmp - 1
            print(number)


t_list = []
for i in range(100):
    t = MyThread(i)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

# 确保子线程执行完毕
print("活跃的线程个数:", threading.active_count())
# 输出最终数值
print("number: ", number)

7.2 Timer计时器

通过threading.Timer类可以实现n秒后执行某操作。注意一个timer对象相当于一个新的子线程。

import time
import threading


class MyThread(threading.Thread):

    def __init__(self, n):
        self.n = n
        # 注意:一定要调用父类的初始化函数,否则不会创建线程
        super().__init__()

    def run(self) -> None:
        while True:
            print(self.n)
            time.sleep(self.n)
            print('============================')


for i in range(1, 5):
    t = MyThread(i)
    if i == 4:
        timer = threading.Timer(5, t.start)  # 5秒后再开始线程4
        timer.start()
    else:
        t.start()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1150028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

winodws10系统C盘文件夹目录讲解

背景: 电脑安装系统一段时间后,发现C盘的空间越来越小,于是乎,想了解一下C盘文件目录结构,删除一下非必要的文件,同时增强一些操作系统的知识。 目前我的C盘目录如下: 如果开启显示隐藏文件&…

希亦内衣洗衣机和美的哪个好?内衣洗衣机对比分析

希亦内衣洗衣机与美的内衣洗衣机,到底哪个更胜一筹?当面对繁多的内衣洗衣机品牌和型号时,如何选购最适合自己的内衣洗衣机?本文将对两大家电品牌——希亦和美的的内衣洗衣机进行深度比较,以帮助你做出最佳决定&#xf…

【EI会议征稿】第三届结构抗震与监测检测国际学术会议(SSRMD 2024)

第三届结构抗震与监测检测国际学术会议(SSRMD 2024) 2024 3rd International Conference on Structural Seismic Resistance, Monitoring and Detection 随着城市化进程的深入,城市中的建筑越来越多。建筑也逐渐多样化,复杂化。…

导弹拦截(最大不上升 或 不下降子序列,dp)

算法分析: 1.求最长子序列 1.每次输入时,计算每个元素对应的序列的长度 1.向前遍历找大于当前元素的数 2.若之前元素对应长度1 大于当前长度,更新当前长度 2.若当前元素对应长度 大于最大长度,更新最大长度 2.dilworth定理 故需…

Vue.js 的核心特点

目录 1.什么是vue 2. 响应式数据绑定 3. 组件化开发 4. 虚拟DOM 5. 双向数据绑定 6. 插件扩展能力 7. 简洁易学 1.什么是vue Vue.js 是一款流行的前端 JavaScript 框架,用于构建用户界面。它是一个轻量级、灵活和易于上手的框架,广泛应用于现代 …

功能型前端项目技术栈选型

PC功能型官网技术栈选择 vue2vuexvue-routerxaxiosanimate主推:vue3piniavue-routertypeScriptaxiosanimate(新技术后期踩坑多,成本较高) 1.2 vue3对比vue2 团队已经熟悉 Vue 2,并且官网的规模不是很大,Vue…

一文理解什么是贝叶斯优化的随机森林

贝叶斯优化 简介 贝叶斯优化是一种启发式的全局优化方法,用于优化那些评估代价高昂且可能带有噪音的黑盒函数。其核心思想是:在每一步,都利用已知的函数评估来构建一个概率模型,预测黑盒函数在未知点上的值,并据此选择…

Java入门与实践

Java基础 Java入门 idea的使用 idea快捷键 crtlaltt 对选中的代码弹出环绕选项弹出层 问题描述:idea光标变小黑块 解决:误触Insert键,再次按Insert键即可 java基础语法 注释 //单行注释/* 多行注释 *//** 文档注释,可提取到…

科普|电源自动测试系统测试的项目都有哪些?

电源自动测试系统是一种用于电源性能自动测试的集成系统,它可以自动检测电源模块或开关电源的输入、输出、保护等各个方面。该系统通常由数据软件和各类硬件测试仪器共同组成,利用通讯总线、测试夹具以及其它线缆等将仪器进行连接组成整体的系统结构&…

【Axure高保真原型】百分比堆叠柱状图

今天和大家分享百分比堆叠柱状图的的原型模板,鼠标移入堆叠柱状图后,会显示数据弹窗,里面可以查看具体项目对应的数据和占比。那这个原型模板是用中继器制作的,所以使用也很方便,只需要在中继器表格里维护项目数据信息…

UVC功能特性(Functional Characteristics)

目录 功能特性视频接口类视频接口子类和协议视频功能拓扑输入终端(Input Terminal)输出终端(Output Terminal)相机终端(Camera Terminal)选择单元(Selector Unit)处理单元(Processing Unit)编码单元(Encoding Unit)拓展单元(Extension Unit) 操作模型(Operational Model)视频接…

数据结构:优先级队列(堆)

概念 优先级队列是啥? 队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列。 在这种情况下, 数据结构应该提供两个最基本的…

在线主动学习算法评估策略:prequential evaluation procedure

在线主动学习算法评估策略:prequential evaluation procedure 在在线主动学习领域(Online Active Learning),对在线主动学习算法的评估策略有多种方法,而现如今常用的方法是prequential evaluation procedure(出自论文《High density-focuse…

计算机网络与技术——数据链路层

😊计算机网络与技术——数据链路层 🚀前言☃️基本概念🥏封装成帧🥏透明传输🥏差错检测 ☃️点对点协议PPP🥏PPP协议的特点🥏PPP协议的帧格式🔍PPP异步传输时透明传输(字…

git-更新项目

进入想要更新的项目目录下,按住shift右击,点击open Git Bash here进入命令行 项目日志 git log 拉取代码 git pull origin developer 将工作区的文件添加到暂存区 git add . 查看文件状态(更改信息) git status 提交暂存区到…

【多线程】线程互斥 {多执行流并发执行的数据竞争问题,互斥锁的基本用法,pthread_mutex系列函数,互斥锁的原理;死锁;可重入函数和线程安全}

一、进程线程间通信的相关概念 临界资源:多线程执行流共享的资源就叫做临界资源。确切的说,临界资源在同一时刻只能被一个执行流访问。临界区:每个线程内部,访问临界资源的代码,就叫做临界区。互斥:通过互…

检测和缓解SQL注入攻击

SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询&#…

一文搞懂 LiveData 粘性事件 和 数据倒灌

文章目录 前言LiveData使用分析粘性事件分析数据倒灌总结 一、前言 在使用LiveData的时候,会有疑问,LiveData 是怎么做到先发送再注册,依然能够接收到事件的。还有就是会碰到切换屏幕,导致重复的操作,也就是所谓的数…

postgresql的windows

1. 资源下载: https://www.postgresql.org/download/windows/ 2. 安装 双击,指定D盘目录,接下来默认安装,一直到出现下面的最后一步。一定要去除勾选复选框。 在最后,点击FINISH。 3. 初始化 4. 检查和修改配置 1&am…

MaxQuant的安装和_使用_(linux)

MaxQuant 是一款定量蛋白质组学软件包,支持多种标记定量和无标定量的质谱数据。 1. 安装 MaxQuant下载方式: 通过官网 下载,包括andromeda(搜索引擎)viewer(检查原始数据、鉴定和定量结果)。通…