Python零基础从小白打怪升级中~~~~~~~多线程

线程安全和锁

一、全局解释器锁

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

GIL全称global interpreter lock,全局解释器锁。

每个线程在执行的时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU。在CPython中,每一个Python线程执行前都需要去获得GIL锁 ,获得该锁的线程才可以执行,没有获得的只能等待 ,当具有GIL锁的线程运行完成后,其他等待的线程就会去争夺GIL锁,这就造成了,在Python中使用多线程,但同一时刻下依旧只有一个线程在运行 ,所以Python多线程其实并不是「并行」的,而是「并发」 。

看到下图,图中是Python中GIL的工作实例,其中有3个线程,线程与线程之间是顺序执行的 ,每个线程开始执行时都会去获得GIL,防止其他线程线程运行 ,每执行完一段时间后,就会释放GIL,让别的线程可以去争夺执行权限,如果自己本身也没有执行完,则本身也会参与这次争夺 。

image.png

# 多线程的代码
import threading, time


def add(n):
    sum = 0
    while sum < n:
        sum += 1
    print(f'sum:{sum}')


if __name__ == '__main__':
    start = time.time()
    n = 500000000
    t1 = threading.Thread(target=add, args=[n // 2])
    t2 = threading.Thread(target=add, args=[n // 2])
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('run time: %s' % str(time.time() - start))

image.png

# 单线程的代码
import time


def add(n):
    sum = 0
    while sum < n:
        sum += 1
    print(f'sum:{sum}')


if __name__ == '__main__':
    start = time.time()
    add(500000000)
    print('run time: %s' % str(time.time() - start))

image.png

总结

  • GIL解决方法:

  • 使用其他语言写的python解释器(不推荐,还是用官方CPython好)
    eg:Jython(java);IronPython(.net);pypy(Python)

  • 不使用多线程,使用多进程-进程里加协程实现多任务来充分利用多核CPU (推荐)

  • 即使存在GIL 在有IO等待操作的程序中,还是多线程快,当然没有资源等待的还是单线程快(科学计算,累加等等)

但需要注意的是线程有了GIL后并不意味着使用Python多线程时不需要考虑线程安全 ,「GIL的存在是为了方便使用C语言编写CPython解释器的编写者,而顶层使用Python时依旧要考虑线程安全」 。

二、线程安全

当多个线程同时访问一个对象时,不管如何计算,如果调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。 如果出现了“脏数据”。则线程不安全。

脏数据 :产生脏数据的原因是,当一个线程在对数据进行修改时,修改到一半时另一个线程读取了未经修改的数据并进行修改。如何避免脏数据的产生呢?一个办法就是用join方法,即先让一个线程执行完毕再执行另一个线程。但这样的本质是把多线程变成了单线程,失去了多线程的意义。另一个办法就是用线程锁。

import threading

g_number = 0


def hello():
    global g_number
    for i in range(1000000):  # 加的次数越大越容易出现资源竞争问题
        g_number += 1
    print(f'thd1运行的结果为:{g_number}')


def world():
    global g_number
    for i in range(1000000):
        g_number += 1
    print(f'thd2运行的结果为:{g_number}')


if __name__ == '__main__':
    thd1 = threading.Thread(target=hello)
    thd2 = threading.Thread(target=world)

    thd1.start()
    thd2.start()

    # 阻塞等待
    thd1.join()
    thd2.join()
    print(g_number)  # 结果随机 可能小于等于2000000

三、锁

锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。

一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。

threading模块中提供了5种最常见的锁,下面是按照功能进行划分:

  • 同步锁:lock(一次只能放行一个)
  • 递归锁:rlock(一次只能放行一个)
  • 条件锁:condition(一次可以放行任意个)
  • 事件锁:event(一次全部放行)
  • 信号量锁:semaphore(一次可以放行特定个)

1、同步锁

同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。

死锁: 指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象。

import threading

g_number = 0
lock = threading.Lock()

def hello():
    global g_number
    for i in range(1000000):  # 加的次数越大越容易出现资源竞争问题
        with lock:
            g_number += 1
    print(f'thd1运行的结果为:{g_number}')


def world():
    global g_number
    for i in range(1000000):
        with lock:
            g_number += 1
    print(f'thd2运行的结果为:{g_number}')


if __name__ == '__main__':
    thd1 = threading.Thread(target=hello)
    thd2 = threading.Thread(target=world)

    thd1.start()
    thd2.start()

    # 阻塞等待
    thd1.join()
    thd2.join()
    print(g_number)  # 结果随机 可能小于等于2000000

2、递归同步锁

在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。

递归锁RLock:它内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

3、条件锁

条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()与notify()来控制线程执行的个数。

注意:条件锁可以自由设定一次放行几个线程。

import threading

currentRunThreadNumber = 0
maxSubThreadNumber = 10


def task():
    global currentRunThreadNumber
    thread_name = threading.currentThread().name

    with condLock:
        print("线程开始启动,并马上进入等待状态 : %s" % thread_name)
        condLock.wait()  # 暂停线程运行、等待唤醒
        print("线程唤醒了,开始运行后面的代码 : %s" % thread_name)
        currentRunThreadNumber += 1


if __name__ == "__main__":
    condLock = threading.Condition()

    for i in range(maxSubThreadNumber):
        subThreadIns = threading.Thread(target=task)
        subThreadIns.start()

    while currentRunThreadNumber < maxSubThreadNumber:
        notifyNumber = int(
            input("请输入要唤醒几个线程:"))

        with condLock:
            condLock.notify(notifyNumber)  # 放行

    print("main thread run end")

4、事件锁

事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。

我们可以将事件锁看为红绿灯,当红灯时所有子线程都暂停运行,并进入“等待”状态,当绿灯时所有子线程都恢复“运行”。

image.png

import threading

maxSubThreadNumber = 3


def task():
    thread_name = threading.currentThread().name
    print("线程开始启动,并马上进入等待状态 : %s" % thread_name)
    eventLock.wait()  # 暂停运行,等待绿灯
    print("第一次绿灯打开,线程往下走:%s" % thread_name)
    eventLock.wait()  # 暂停运行,等待绿灯
    print("第二次绿灯打开,线程往下走:%s" % thread_name)


if __name__ == "__main__":
    eventLock = threading.Event()
    for i in range(maxSubThreadNumber):
        subThreadIns = threading.Thread(target=task)
        subThreadIns.start()
    eventLock.set()  # 设置为绿灯
    eventLock.clear()  # 设置为红灯
    eventLock.set()


5、信号量锁

Semaphore()

信号量锁也是根据条件锁来做的,它与条件锁和事件锁的区别如下:

  • 条件锁:一次可以放行任意个处于“等待”状态的线程
  • 事件锁:一次可以放行全部的处于“等待”状态的线程
  • 信号量锁:通过规定,成批的放行特定(指定)个处于“上锁”状态的线程
import threading
import time

maxSubThreadNumber = 6


def task():
    thread_name = threading.currentThread().name
    with semaLock:
        print("线程获得锁,开始运行: %s" % thread_name)
        time.sleep(3)


if __name__ == "__main__":

    semaLock = threading.Semaphore(2)

    for i in range(maxSubThreadNumber):
        subThreadIns = threading.Thread(target=task)
        subThreadIns.start()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL(2024.4.17)

目录 1. 什么是MySQL的MVCC机制&#xff1f; 2. 如何理解InnoDB的Next-Key Lock机制&#xff1f; 3. 快照读和当前读的区别&#xff1f; 4. 如何在SQL语句中触发当前读&#xff1f; 5. MySQL默认的隔离级别是什么&#xff1f; 6. 如何避免在使用当前读时可能出现的死锁问…

Docker部署metahuman-stream数字人系统

metahuman-stream是基于ernerf模型的流式数字人&#xff0c;实现音视频同步对话。 metahuman-stream xtts-streaming-server srs 部署 srs # rtmpserver docker run -it -d \ -p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp -p 10080:10080/udp \ --name srs \ reg…

Postgresql源码(126)TupleStore使用场景与原理分析

相关 《Postgresql源码&#xff08;125&#xff09;游标恢复执行的原理分析》 《Postgresql游标使用介绍&#xff08;cursor&#xff09;》 总结 开源PG中使用tuple store来缓存tuple集&#xff0c;默认使用work_mem空间存放&#xff0c;超过可以落盘。在PL的returns setof场景…

基于51单片机的步进电机调速系统设计

基于51单片机的步进电机调速系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.按键可以控制电机正、反转&#xff0c;加、减速&#xff0c;停止&#xff1b; 2.一位7段数码管实时显示档位&#xf…

6个免费的伪原创工具,轻松生成原创文章

如今&#xff0c;内容创作已经成为许多人关注的焦点。然而&#xff0c;随之而来的是创作压力和时间成本的增加。为了解决这些问题&#xff0c;越来越多的人开始寻找一些伪原创工具来帮助他们生成原创文章&#xff0c;其中免费的伪原创工具成为了热门选择之一。这些免费的伪原创…

建都寿春的袁术兴亡史

三国(220年-280年)是中国历史上位于汉朝之后&#xff0c;晋朝之前的一段历史时期。这一个时期&#xff0c;先后出现了曹魏、蜀汉、东吴三个主要政权。袁术的地盘很小&#xff0c;为了在三国时期能够立足&#xff1f; 事实上&#xff0c;袁术巅峰时期的地盘并不小&#xff0c;而…

类和对象中-运算符重载

在C中&#xff0c;有些成员函数如果我们不去显示定义&#xff0c;编译器会自动生成 会自动生成的特殊函数&#xff1a; 他们不能定义为全局函数&#xff0c;必须是类成员员函数&#xff08;特别是拷贝赋值重载&#xff09; 下面介绍默认生成函数的作用&#xff0c;特点 构造 …

工作流JBPM流程图说明

文章目录 5☃️ 相关概念6 ☃️流程图说明6.0 ❄️❄️快速上手6.1 ❄️❄️活动Activity / 节点Node6.1.1 start 开始活动6.1.2 end 结束活动6.1.3 task 任务活动6.1.4 decision 判断活动6.1.5 fork/join 分支/聚合活动 6.2 ❄️❄️流转 Transition / 连线 &#xff08;单向箭…

Python相关性分析

分析连续变量之间线性相关程度的强弱&#xff0c;并用适当的统计指标表示出来的过程称为相关分析。 可以直接绘制散点图&#xff0c;或者绘制散点图矩阵&#xff0c;或者计算相关系数来进行相关分析。 相关系数的计算如下所示&#xff1a; 示例数据&#xff1a; 计算百合酱蒸…

云服务器安装Mysql、MariaDB、Redis、tomcat、nginx

前置工作 进入根目录 cd / 都在/usr/local/src文件夹&#xff09; 上传压缩包 rz 压缩包 Mysql 1.下载并安装MySQL官方的 Yum Repository wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum…

lua 光速入门

文章目录 安装注释字符串变量逻辑运算条件判断循环函数Table (表)常用全局函数模块化 首先明确 lua 和 js Python一样是动态解释性语言&#xff0c;需要解释器执行。并且不同于 Python 的强类型与 js 的弱类型&#xff0c;它有点居中&#xff0c;倾向于强类型。 安装 下载解释…

美易官方:人民币国际支付占比升至近5%

随着全球金融市场的不断发展和数字化进程的加速&#xff0c;人民币的国际支付地位逐渐提升&#xff0c;成为备受瞩目的焦点。最近的数据显示&#xff0c;人民币在国际支付中的占比已经升至近5%&#xff0c;自11月以来已成为第四大交易货币。这一变化不仅反映了中国经济的崛起和…

Python 密码学实用指南(全)

原文&#xff1a;zh.annas-archive.org/md5/fe5e9f4d664790ea92fb33d78ca9108d 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 密码学在保护关键系统和敏感信息方面有着悠久而重要的历史。本书将向您展示如何使用 Python 加密、评估、比较和攻击数据。总的来说&…

MDK stm32怎么生成bin文件

第一种 D:\Keil_v5\ARM\ac5.6\bin\fromelf.exe --bin -o ../../Output/atk_f407.bin ../../Output/atk_f407.axf 空格解析 D:\Keil_v5\ARM\ac5.6\bin\fromelf.exe一个空格--bin一个空格-o两个空格../../Output/atk_f407.bin ../../Output/atk_f407.axf &#xff08;注意后…

Rose中UML类图详解及画法

一、UML简介 UML&#xff08;Unified Modeling Language&#xff09;是一种用于软件系统建模的标准化工具&#xff0c;它提供了一套统一的符号和语法&#xff0c;用于描述、设计、构建和交流软件系统的结构和行为。UML广泛应用于软件开发领域&#xff0c;被视为一种通用的建模语…

【C语言】qsort()函数排序及其模拟实现,万物皆可排!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 函数介绍 2. qsort举例排列整型变量 3. qsort举例排列结构型变量 3.1 按名字排序 3.1.1 srtcmp函数 3.2 按年龄排序 4. qsort函数模拟实现(采用冒泡的…

宝塔要注意的问题

数据库创建访问权限要全部人 反向代理1 打包dist,并不会有反向代理&#xff0c;所以宝塔里面要配置 反向代理2 这种去掉/api为/&#xff0c;上面的并没有去掉 rewrite ^/api/(.*)$ /$1 break;

mysql数据库表的数据显示到前端tableView

首先我们在ui视图设计中引入TableView, 定义一个model QSqlQueryModel *modelnew QSqlQueryModel(ui->tableView);model->setQuery(query);//将查询结果绑定到模型上ui->tableView->setModel(model); 将tableView内容设置成model然后就可以出现数据库的数据。示…

uni.uploadFile上传图片后台接收不到数据

今天遇到一个很奇怪的问题&#xff0c;通过使用uni.uploadFile上传文件时后端接收不到文件&#xff0c;查过很多资料&#xff0c;原来是自定义了header的Content-Type问题。取消即可&#xff0c;另把自定义文件上传的代码贴出来。 分析&#xff1a;当我们加上请求头的时候 不…

一.NODE MCU(ESP8285,ESP8286)开发环境搭建

一.序言: 1.esp8285长什么样? 2.esp8285是什么,能做什么? 通过上面图片,看到上面的芯片,是带有多个阵脚的单片机。实际上,看着该芯片很小,但是却具有完整的wifi无线蓝牙功能,它本身可以运行一个极简的linux小系统,并且该极简的小linux系统具备无线蓝牙功能。。它同…