【Python实战】global关键字的应用和线程并发

本文介绍了Python中全局变量的使用,包括global关键字的运用、线程间共享全局变量可能导致的问题以及如何通过使用锁保证线程安全,以实现原子性操作。作者通过示例展示了如何在异步多线程环境中管理全局变量并防止竞态条件。
摘要由CSDN通过智能技术生成

请添加图片描述

在很多场景和业务实践中,线程之间的变量共享和保持原子性非常的关键和重要。

这边主要解决在代码实践中,如何在异步多线程中改变同一个变量和保持这个变量的原子性。

一、前言

编译环境

Python版本:3.11.7

IDE:PyCharm 2023.3.2


二、gloabl全局变量关键字

在Python中,global 是一个关键字,用于在函数内部声明一个全局变量。

当你在一个函数内部想要修改全局作用域的变量时,就需要使用 global 关键字。

以下示例展示如何在方法中改变全局变量的值。

代码示例


x = 10  # 全局变量

def modify_global_variable():

    global x  # 使用global关键字声明要修改的是全局变量x
    
    x = 20

modify_global_variable()

print(x)  # 打印全局变量x的值,输出为 20


三、程序运行时全局变量的变化

在项目实战中,必不可少的是如何实时改变这个全局变量的值来配合业务上各种需求的使用。

以下示例让我们来看一下, 全局变量的值在实时改变中,是怎么实现的。

代码示例

import threading
from time import sleep

# 初始化一个变量列表用来实时改变全局变量
variable_list = ['Qian', 'Kun', 'Li', 'Kan', 'Dui', 'Zhen', 'Xun', 'Gen']

# 初始化变量a
a = ''

# 初始化变量b
b = ''


# 此方法用来实现在程序运行中,不停的切换变量a和变量b的值
def divination():

    # 声明全局变量a和b
    global a, b
    
    while True:
    
        # 循环列表中的值赋给变量a和b
        for i in range(len(variable_list)):
        
            a = variable_list[i]
            
            # 这边用三目来判断一下索引,避免索引异常
            b = variable_list[i + 1] if i < len(variable_list) - 1 else variable_list[0]
            
            # 每一秒改变一次变量a和b的值
            sleep(1)


# 用异步线程来实现变量切换,用守护线程来让线程在后台运行
threading.Thread(target=divination, daemon=True).start()


# 主线程,用来打印变量a和b的实时变化
while True:

    # 这边使用同一行覆盖打印
    print(f'\r变量a: {a},变量b:{b}', end='', flush=True)
    
    # 后台线程运行和主线程之间会有几毫秒的间隔
    # 为了确保打印变量a和b每次改变的值
    # 这边每次循环都延迟10ms
    sleep(1.01)


四、全局变量的线程安全问题

在很多业务情况中,会经常出现线程之间互相竞争导致阻塞等等问题

这边我们尝试模拟一下在Python中的类似情况

代码如下:

import threading

# 初始化计数值
counter = 0


def increment_counter():

    global counter
    
    # 读取当前计数值
    current_value = counter
    
    # 模拟一些耗时的计算
    # 在这里,可能会有其他线程修改了 counter 的值
    # 在实际应用中,这个操作可能会更加复杂
    for _ in range(1000000):
        pass
        
    # 增加计数值
    counter = current_value + 1


# 创建两个线程同时修改计数值
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("最后计数:", counter)

运行结果counter多数为1,偶尔会为2

在这个例子中,increment_counter 函数首先读取当前计数值,然后进行一些模拟的耗时计算,最后将计数值加一写回到 counter。

由于这个操作不是原子的,两个线程可能同时读取相同的 counter 值,并在模拟计算之后分别增加它,导致最终的计数值不是期望的结果。

为了解决这个问题,可以使用锁来确保对 counter 的操作是原子的。

代码如下:

import threading

# 初始化计数值
counter = 0

# 创建锁
counter_lock = threading.Lock()

def increment_counter():

    global counter
    
    with counter_lock:
    
        # 读取当前计数值
        current_value = counter
        
        # 模拟一些耗时的计算
        for _ in range(1000000):
            pass
            
        # 增加计数值
        counter = current_value + 1

# 创建两个线程同时修改计数值
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("z最后计数:", counter)

运行结果始终为2

通过使用锁,确保在任何时刻只有一个线程可以执行对 counter 的操作,从而避免了竞争条件。


五、总结

全局变量在使用过程中,得注意非原子操作变量产生竞争条件导致结果不一致的问题

在高并发情况下,一定不要忘记在使用global关键字时,使用threading.Lock()来保证线程安全

在这里插入图片描述
我欲乘风归去,又恐琼楼玉宇。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愛彈吉他的小盆友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值