Python的线程05 解决共享变量问题举例

本文介绍了Python中多线程环境下因共享变量导致的转账问题,通过实例展示了当多个线程同时修改同一变量时,可能会出现预期外的结果。通过改造代码,将共享变量改为每个线程独立的参数,从而解决了并发修改导致的错误。最终实现无论运行多少次,都能得到正确的账户余额。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正式的Python专栏第43篇,同学站住,别错过这个从0开始的文章!

前面学委分享了几篇多线程的文章,前面提到了银行转账这个场景,展示了一个比较耗时的转账操作。

这篇继续转帐,下面展示一段程序,多个线程的操作都更改了amount变量导致运行结果不对的问题

前文说了转账问题

下面展示另一种转账的方式:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/24 12:02 上午
# @Author : LeiXueWei
# @优快云/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : __init__.py.py
# @Project : hello
import random
import threading
import datetime
import time

xuewei = {'balance': 157}


# amount为负数即是转出金额
def transfer(money):
    name = threading.current_thread().getName()
    print("%s 给xuewei转账 %s " % (name, money))
    xuewei['balance'] += money
    print("xuewei账户余额:", xuewei['balance'])


lists = [-7, 20, -20, 7]  # 4次转账的数额,负数为学委的账户转出,正数为他人转入。
# 创建4个任务给学委转账上面lists的金额
threads = []
for i in range(4):
    amount = lists[i]
    name = "t-" + str(i)
    print("%s 计划转账 %s" % (name, amount))
    mythread = threading.Thread(name=name, target=lambda: transfer(amount))
    threads.append(mythread)

# 开始转账
for t in threads:
    t.start()

# 等待3秒让上面的转账任务都完成,我们在看看账户余额
time.sleep(3)
print("-" * 16)
print("学委账户余额:", xuewei['balance'])

这里启动了4个线程,每个线程内有个lambda表达式,分别于学委的账户进行转账,但是最后结果是185. 而不是157.

下面是运行结果:

屏幕快照 2021-11-24 上午12.48.35.png

PS: 这只是一种运行结果。多线程的运行结果不是永远一样的。

如何解决这个问题?

根据观测结果我们发先amount只保留了最后一个值。

好,下面改造一下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/24 12:02 上午
# @Author : LeiXueWei
# @优快云/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : __init__.py.py
# @Project : hello
import random
import threading
import datetime
import time

xuewei = {'balance': 157}

lists = [-7, 20, -20, 7]  # 4次转账的数额,负数为学委的账户转出,正数为他人转入。


def transfer(amount):
    name = threading.current_thread().getName()
    print("%s 给xuewei转账 %s " % (name,amount))
    xuewei['balance'] += amount
    print("xuewei账户余额:", xuewei['balance'])


# 创建4个任务给学委转账上面lists的金额
for i in range(4):
    amount = lists[i]
    name = str(i)
    # mythread = threading.Thread(name=name, target=lambda: transfer(amount))
    def event():
        print("%s 计划转账 %s" % (name, amount))
        transfer(amount)
    mythread = threading.Thread(name=name, target=event)
    mythread.start()


# 等待3秒让上面的转账任务都完成,我们在看看账户余额
time.sleep(3)
print("-" * 16)
print("学委账户余额:", xuewei['balance'])

学委这里加了一个event函数,把转账计划打印出来。

从下面的一次运行结果看,event函数的输出结果没错,所有”计划转账“金额都如预期[-7, 20, -20 7]。 问题是transfer函数再多线程执行的时候,我们发现amount被多线程竞争修改了:

用户0转账金额变成20
用户1转账金额变成-20
用户2转账金额变成7
用户3转账金额变成7

屏幕快照 2021-11-25 上午9.07.11.png

也就是说,amount被后面的线程修改了,但是前面线程还没有执行完。
用户0应该转账-7的,中间还没有执行完毕,结果被线程1修改了amount为20,用户0继续执行转账,余额变成177. 其他依次推理。

amount这个变量被多个线程竞争修改了,这个就是程序的共享变量

到底如何解决?

方法非常简单:直接干掉共享变量。

下面就是消除共享变量的方法: 让共享变成每个线程访问独立运行空间

所以代码改动如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/24 12:02 上午
# @Author : LeiXueWei
# @优快云/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : __init__.py.py
# @Project : hello
import random
import threading
import datetime
import time

xuewei = {'balance': 157}



lists = [-7, 20, -20, 7]  # 4次转账的数额,负数为学委的账户转出,正数为他人转入。
# 我们不要依赖amount变量了
def transfer():
    name = threading.current_thread().getName()
    xuewei['balance'] += lists[int(name)] #通过线程名字来获取对应金额
    print("xuewei账户余额:", xuewei['balance'])

# 创建4个任务给学委转账上面lists的金额
threads = []
for i in range(4):
    amount = lists[i]
    name = str(i)
    print("%s 计划转账 %s" % (name, amount))
    # mythread = threading.Thread(name=name, target=lambda: transfer())
    def event():
        transfer()
    mythread = threading.Thread(name=name, target=event)
    threads.append(mythread)

# 开始转账
for t in threads:
    t.start()

# 等待3秒让上面的转账任务都完成,我们在看看账户余额
time.sleep(3)
print("-" * 16)
print("学委账户余额:", xuewei['balance'])

运行结果如下:

屏幕快照 2021-11-25 上午12.04.44.png

上面的代码不管怎么运行,运行多少次最后学委的账户都是157.(PS:学委不会联系读者转账的,这个特别注意)。

这次展示的另一种方式来避开多线程出现bug的方法,使用一个list下标跟线程名字一一对应,这样只要是对应名字的线程拿到的数值不错错乱。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!
编程很有趣,关键是把技术搞透彻讲明白。
欢迎关注微信,点赞支持收藏!

<think>好的,用户的问题是关于Python线程环境下如何实现数据共享。我需要先回顾一下相关知识,确保回答准确。 首先,Python的多线程因为GIL的存在,同一时间只能有一个线程执行字节码,这对CPU密集型任务有限制,但IO密集型任务还是可以受益。用户提到数据共享,所以需要说明线程之间如何共享数据,以及可能的同步机制。 根据引用[2],多线程中每个线程有自己的内存空间,不能直接共享内存,但进程的资源是共享的。所以实际上,线程可以访问同一进程的全局变量。不过,这可能导致竞争条件,需要锁机制来保护数据。 然后,引用[3]提到线程共享进程的内存空间,所以全局变量和数据结构是共享的。但需要线程安全的方式操作它们,比如使用Lock、RLock、Event等同步原语。 用户可能想知道具体有哪些方法,比如使用队列、共享内存结构,或者第三方库。需要列举常见的几种方式,并举例说明,比如使用threading.Lock()来保护共享变量,或者使用Queue模块的队列进行线程间通信。 还要注意,虽然线程可以共享数据,但不当的同步会导致死锁或数据不一致。因此,需要强调正确使用锁的重要性,以及何时该使用其他结构如Condition或Semaphore。 可能用户对多线程和多进程的数据共享方式有混淆,需要区分。根据引用[2],多线程通过全局变量共享,而多进程需要其他机制如共享内存。但问题明确是多线程,所以重点放在线程共享方法上。 需要检查是否有遗漏的实现方式,比如使用线程安全的容器如queue.Queue,或者使用Value和Array从multiprocessing模块,但要注意这些可能更常用于多进程,不过multiprocessing模块有些结构也可以在多线程中使用,但需确认。 最后,根据用户提供的引用,引用[1]提到文件锁,但用户的问题更广泛,涉及一般数据共享,所以可能需要提到文件锁仅在涉及文件IO时使用,而内存中的数据共享使用其他方法。 总结回答的结构:先说明线程共享内存的特性,然后列出几种实现方式,如全局变量加锁、使用队列、使用线程安全的数据结构,并给出示例代码。同时提醒注意事项,比如GIL的影响和同步的重要性。 需要确保示例代码正确,比如正确使用with语句管理锁,避免死锁。另外,引用相关文献,如引用[2]和[3]来支持线程共享内存的观点,引用[1]可能仅在文件操作时相关。 可能用户的实际应用场景是数据处理或并发IO操作,所以例子应贴近这些情况,比如计数器、任务队列等。 现在组织内容,分点回答,确保清晰易懂,符合用户要求的结构。</think>在Python线程环境下,由于线程共享进程的内存空间,数据共享主要通过以下方式实现: ### 一、全局变量共享 所有线程默认共享全局变量,但需通过锁机制保证线程安全: ```python import threading counter = 0 lock = threading.Lock() def increment(): global counter with lock: counter += 1 threads = [] for _ in range(10): t = threading.Thread(target=increment) threads.append(t) t.start() for t in threads: t.join() print(counter) # 输出10 ``` 此方式适合简单计数场景,需注意$lock.acquire()$和$lock.release()$的成对使用[^2]。 ### 二、队列(Queue)通信 使用线程安全的`queue.Queue`实现生产者-消费者模式: ```python from queue import Queue import threading data_queue = Queue() def producer(): for i in range(5): data_queue.put(i) def consumer(): while True: item = data_queue.get() print(f"Processed: {item}") data_queue.task_done() t1 = threading.Thread(target=producer) t2 = threading.Thread(target=consumer) t1.start(); t2.start() t1.join(); data_queue.join() ``` 队列内部已实现锁机制,适合任务分发场景[^3]。 ### 三、共享内存结构 使用`threading.local()`创建线程局部数据: ```python shared_data = threading.local() def show_data(): print(shared_data.value) def worker(value): shared_data.value = value show_data() threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)] for t in threads: t.start() for t in threads: t.join() ``` 该方式实现线程隔离的共享数据,每个线程独立操作自己的数据副本。 ### 四、高级同步机制 1. **事件(Event)**:通过`threading.Event()`实现线程间状态通知 2. **条件变量(Condition)**:使用`threading.Condition()`实现复杂同步逻辑 3. **信号量(Semaphore)**:通过`threading.Semaphore(n)`控制并发访问数量 ### 注意事项 1. GIL导致多线程不适合CPU密集型任务,建议改用多进程[^2] 2. 文件操作时需额外加文件锁(如`fcntl`模块)[^1] 3. 优先使用`with lock`语句避免死锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雷学委

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

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

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

打赏作者

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

抵扣说明:

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

余额充值