python中线程使用与全局变量加锁

本文详细介绍了在Python中如何使用互斥锁(mutex)进行线程间的同步,防止多个线程同时修改全局变量导致的数据不一致问题。通过两个函数func1和func2的实例,展示了如何在循环中正确地获取和释放锁,确保数据的安全性和一致性。

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

# -*- coding:utf-8 -*-
import threading
import time
from time import sleep, ctime

g_num = 0
# 创建互斥锁,默认不上锁
mutex = threading.Lock()


def func1(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁
    print('--func1--g_num:%d' % g_num)


def func2(num):
    global g_num
    for i in range(num):
        mutex.acquire()
        g_num += 1
        mutex.release()
    print('--func2--g_num:%d' % g_num)


if __name__ == "__main__":
    t1 = threading.Thread(target=func1, args=(100000,))
    t1.start()
    t2 = threading.Thread(target=func2, args=(100000,))
    t2.start()

    while True:
        length = len(threading.enumerate())
        print('now the all thread:%d' % length)
        if length <= 1:
            break

        sleep(1)

    print('the result--%d' % g_num)

<think>我们面对的问题是在Python多线环境下,一个串口接收线修改全局变量,其他线读取并打印该变量的值。由于多线共享全局变量存在线安全问题,我们需要使用锁(Lock)来确保同一时间只有一个线可以修改该变量,避免竞争条件。 根据引用[1]和[3],我们了解到多线中需要处理线同步。在Python中,可以使用`threading.Lock`(互斥锁)或`threading.RLock`(可重入锁)来保护共享资源。另外,引用[3]提到自旋锁(SpinLock)是一种轻量级锁,但在Python标准库中并没有直接提供自旋锁,通常我们使用互斥锁。 方案设计: 1. 创建一个全局变量(例如`global_data`)用于存储串口接收的数据。 2. 创建一个锁对象(例如`lock`)用于保护对全局变量的访问。 3. 在串口接收线中,每当接收到新数据时,获取锁,更新全局变量,然后释放锁。 4. 在其他线中,若需要读取该全局变量,同样需要先获取锁,读取后释放锁。 注意:为了避免死锁,确保在获取锁后,无论发生什么情况都要释放锁(通常使用`try...finally`或上下文管理器)。 下面是一个示例代码: ```python import threading import serial import time # 全局变量和锁 global_data = None # 存储串口接收的数据 lock = threading.Lock() # 保护global_data的锁 # 串口接收线函数 def serial_receiver(ser): global global_data while not exit_flag: if ser.in_waiting: data = ser.readline().decode('utf-8').strip() # 使用锁更新全局变量 with lock: global_data = data # 这里我们只存储最新的一行数据 # 如果需要存储所有数据,可以使用列表,但注意内存增长 time.sleep(0.01) # 避免过度占用CPU # 其他线(例如打印线)函数 def data_printer(): global global_data while not exit_flag: # 使用锁读取全局变量 with lock: current_data = global_data # 读取当前值 if current_data is not None: print(f"当前接收的数据: {current_data}") time.sleep(1) # 每秒打印一次 # 主序 if __name__ == "__main__": # 初始化串口 ser = serial.Serial('COM3', 9600, timeout=1) exit_flag = False # 用于控制线退出 # 创建线 receiver_thread = threading.Thread(target=serial_receiver, args=(ser,)) printer_thread = threading.Thread(target=data_printer) # 启动线 receiver_thread.start() printer_thread.start() try: # 主线等待用户输入以退出 input("按回车键退出...\n") finally: exit_flag = True receiver_thread.join() printer_thread.join() ser.close() print("序已退出") ``` 说明: 1. 我们使用一个全局变量`global_data`来存储串口接收的最新一行数据(注意:如果数据量很大且需要历史数据,应使用队列或其他数据结构)。 2. 使用`threading.Lock`来保护对`global_data`的读写。在串口线更新数据时和打印线读取数据时,都通过`with lock`语句获取锁,确保操作的原子性。 3. 设置了一个`exit_flag`全局变量(这里没有用锁保护,因为Python中对于简单变量的赋值操作是原子的,但更严谨的做法是使用锁或者使用`threading.Event`)来通知线退出。 注意:在打印线中,我们每秒打印一次,实际应用中可以根据需求调整。 另外,如果串口数据速率很高,打印线可能会打印出过时的数据(因为打印线每秒打印一次,而串口线可能已经更新了多次)。如果你需要实时打印,可以在打印线中减少睡眠时间,或者使用事件驱动机制(例如,当串口线更新数据后,设置一个事件,然后打印线等待该事件,事件触发后立即打印)。 扩展:如果多个线都需要读取这个全局变量使用锁是安全的。但是,如果读取非常频繁,可能会影响性能。在这种情况下,可以考虑使用不可变对象(例如每次更新都创建一个新的字符串或元组),这样读取线在没有锁的情况下也可以读取(因为不可变对象在更新时不会处于不一致状态)。但是,在Python中,赋值操作是原子的(对于单个变量),所以即使没有锁,读取一个整数或字符串也不会看到中间状态。然而,为了确保更新操作的可见性(因为内存可见性问题)和多个相关变量的原子更新,我们仍然需要使用锁。 但是,注意:在Python中,由于GIL(全局解释器锁)的存在,对于单个变量的赋值操作是原子的(例如整数、字符串等),所以上述代码中,即使没有锁,读取和赋值`global_data`(单个变量)也是原子的。但是,如果我们更新的是多个变量(例如一个列表,我们更新列表的内容然后赋值给全局变量),那么就需要锁来保证多个操作的原子性。另外,使用锁也可以确保更新后其他线能立即看到最新的值(因为锁的获取和释放会涉及内存屏障)。 因此,为了代码的健壮性和可扩展性,建议使用锁。 如果我们需要存储多行数据,可以使用队列(`queue.Queue`),它是线安全的,不需要额外加锁。但这里我们只要求存储最新的一行,所以使用一个变量加锁即可。 相关问题: 1. 在Python多线中,为什么有时不使用锁也能正常运行? 答:因为Python有GIL,单个字节码指令的执行是原子的,但是多个操作(例如多个赋值)就不是原子的。另外,内存可见性问题在Python中由于GIL的存在,通常不会出现,但为了确保代码的健壮性,还是应该使用锁[^1][^3]。 2. 除了使用锁,还有哪些方法可以实现线间共享数据? 答:可以使用线安全的数据结构,如`queue.Queue`,或者使用`threading.Event`、条件变量(`threading.Condition`)等同步原语,也可以使用线本地存储(`threading.local`)避免共享[^1][^2]。 3. 在串口通信中,如何处理高速数据流? 答:可以使用缓冲区(如队列)存储数据,然后由工作线处理,避免数据丢失。同时,注意线间的协调,避免生产者(串口线)和消费者(处理线)速度不匹配[^2]。 4. 如何优雅地停止线? 答:通常使用一个标志位(如`exit_flag`)来通知线退出。对于阻塞操作(如`ser.readline()`),可以设置串口的超时(timeout)或者使用非阻塞IO,然后循环检查退出标志[^1]。</think># Python 多线环境下串口接收全局变量同步方案 在 Python 多线环境中实现串口接收线修改全局变量并在其他线中打印该变量值,需要解决线安全问题和同步机制。以下是完整的解决方案: ```python import threading import serial import time # 全局共享数据类(线安全) class SharedData: def __init__(self): self.lock = threading.Lock() # 互斥锁保护共享资源 self.exit_flag = False # 线退出标志 self.serial_data = None # 串口接收数据存储 self.data_ready = False # 数据就绪标志 # 串口接收线 def serial_receiver(ser, shared): print("串口接收线启动") while not shared.exit_flag: try: if ser.in_waiting: # 读取串口数据 data = ser.readline().decode('utf-8').strip() # 使用锁安全更新共享数据 with shared.lock: shared.serial_data = data shared.data_ready = True # 设置数据就绪标志 except Exception as e: print(f"串口读取错误: {e}") time.sleep(0.01) # 避免过度占用CPU print("串口接收线退出") # 数据处理线 def data_processor(shared): print("数据处理线启动") while not shared.exit_flag: # 检查数据就绪标志 with shared.lock: if shared.data_ready: # 获取数据并重置标志 data = shared.serial_data shared.data_ready = False else: data = None # 处理数据 if data: print(f"[处理线] 接收到数据: {data}") time.sleep(0.1) # 控制处理频率 print("数据处理线退出") # 主函数 def main(): # 创建共享对象 shared = SharedData() # 初始化串口 (根据实际设备修改参数) try: ser = serial.Serial( port='COM3', # Windows端口示例 baudrate=9600, timeout=1 ) except serial.SerialException as e: print(f"串口打开失败: {e}") return # 创建并启动线 receiver_thread = threading.Thread( target=serial_receiver, args=(ser, shared), daemon=True ) processor_thread = threading.Thread( target=data_processor, args=(shared,), daemon=True ) receiver_thread.start() processor_thread.start() # 主线控制 try: print("序运行中... 输入 'exit' 退出") while True: command = input().strip().lower() if command == "exit": with shared.lock: shared.exit_flag = True break finally: # 等待线结束 receiver_thread.join(timeout=1.0) processor_thread.join(timeout=1.0) ser.close() print("序已退出") if __name__ == "__main__": main() ``` ## 关键实现说明 ### 1. 线同步机制 - **互斥锁 (Lock)**:使用 `threading.Lock` 保护共享资源,确保对全局变量的访问是原子的[^3] - **数据就绪标志**:使用 `data_ready` 标志通知其他线有新数据可用 - **退出标志**:使用 `exit_flag` 安全终止所有线 ### 2. 线分工 - **串口接收线**:专责读取串口数据并更新共享变量 - **数据处理线**:检查数据就绪标志并处理新数据 - **主线**:提供用户控制接口 ### 3. 共享数据结构 ```python class SharedData: def __init__(self): self.lock = threading.Lock() # 互斥锁 self.exit_flag = False # 线退出标志 self.serial_data = None # 串口数据存储 self.data_ready = False # 数据就绪标志 ``` ## 数据流说明 1. 串口接收线读取数据 2. 获取锁保护共享资源 3. 更新 `serial_data` 并设置 `data_ready = True` 4. 释放锁 5. 处理线检查 `data_ready` 标志 6. 当标志为真时,获取锁并读取数据 7. 读取后重置 `data_ready = False` 8. 释放锁并处理数据 ## 注意事项 1. **线安全**:所有对共享资源的访问必须通过锁保护 2. **锁的范围**:保持锁内操作尽可能简短,避免长时间阻塞其他线 3. **数据解码**:根据设备协议调整 `decode()` 方法 4. **异常处理**:添加适当的异常处理确保序健壮性 5. **性能考量**:调整 `time.sleep()` 参数平衡CPU使用率 ## 扩展建议 1. **使用队列替代共享变量**:对于高频数据,考虑使用 `queue.Queue` 实现生产者-消费者模式 2. **添加数据校验**:实现CRC校验等机制确保数据完整性 3. **实现超时处理**:添加数据接收超时检测 4. **支持多个串口**:扩展为管理多个串口设备的架构 > 此方案通过互斥锁保护共享资源,使用标志位实现线间通信,确保串口数据在多线环境中的安全传递[^1][^3]。在Linux环境下开发时,还需遵循POSIX标准API和系统调用规范[^2]。 ## 相关问题 1. Python中Lock和RLock有何区别?何时应使用RLock?[^1] 2. 如何实现串口通信中的超时重连机制? 3. 在多线环境下,使用队列(Queue)相比直接共享变量有哪些优势?[^3] 4. Linux系统下多线需要注意哪些POSIX标准?[^2] 5. 如何扩展此方案以支持多个串口设备同时工作?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力减肥的小胖子5

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

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

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

打赏作者

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

抵扣说明:

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

余额充值