讲师中心 微信公众号
AI工具推荐 视频效率加速

高级抽象:构建稳健的并发串口通信机制

陌墨吖_2548

陌墨吖_2548

发布时间:2025-07-03 21:30:35

|

892人浏览过

|

来源于php中文网

原创

高级抽象:构建稳健的并发串口通信机制

在多线程环境中对串行设备进行并发访问时,如何通过高级抽象解决底层同步问题。针对串行设备通常遵循的请求-响应协议,文章提出了两种主要策略:一是采用专用的串行通信线程结合消息队列进行请求序列化;二是利用互斥锁(Mutex)确保对串行端口的独占访问。通过这些方法,可以有效避免数据损坏和协议违规,实现稳定可靠的并发串行通信。

在嵌入式系统或PC与外部硬件设备通信的场景中,串行通信(如UART、RS232/485)扮演着核心角色。当多个应用线程需要同时向同一串行设备发送查询或接收数据时,如何构建一个高层次的抽象层,以屏蔽底层的并发控制复杂性,成为了一个关键问题。本文将深入探讨这一挑战,并提供两种主流的解决方案。

理解串行通信的挑战:请求-响应协议与并发

大多数串行设备,特别是简单的从属设备,都遵循严格的请求-响应协议。这意味着设备在接收到一个查询(如“foo”或“bar”)后,会一直忙碌直到发送回相应的响应。在此期间,设备通常无法处理任何新的请求。因此,主机或主设备必须尊重并强制执行这一协议:一次请求消息的传输必须紧随其后是响应的接收(或适当的超时),在当前请求-响应周期完成之前,绝不能开始另一次请求消息的传输。

直接让多个线程并行地向串行端口写入和读取,如以下简化示例所示,将导致数据混乱或协议违规:

def get(query):
    serial_port.write(query)
    data = serial_port.read(8)
    return data

这种方式的问题不在于位或字节层面的数据混合(因为操作系统内核驱动程序会处理底层的I/O,线程无法直接访问硬件并同时发送位),而在于它没有强制执行请求-响应协议。如果一个线程在设备尚未响应前发送了另一个查询,或者在读取响应完成前另一个线程开始写入,都将导致通信失败或数据错乱。核心问题是缺乏同步机制来确保对串行端口的独占访问和协议的正确执行。

解决方案一:基于消息队列的专用串行通信线程

一种优雅且高度抽象的解决方案是引入一个专用的线程来管理串行端口的所有I/O操作。这个线程充当串行通信的“守门员”,负责处理所有发送到设备的请求和接收设备的响应。其他需要与串行设备交互的线程,不再直接操作串行端口,而是将它们的请求封装成消息,发送到一个共享的消息队列中。

工作原理:

  1. 请求队列: 主通信线程维护一个请求队列。当其他线程需要发送数据时,它们将请求(通常包括要发送的查询内容和接收响应的回调函数或用于通知的同步对象)放入此队列。
  2. 顺序处理: 主通信线程从队列中按顺序取出请求。
  3. 发送与接收: 对于每个请求,主通信线程执行完整的请求-响应周期:发送查询,然后阻塞式等待设备的响应。
  4. 响应回传: 收到响应后,主通信线程将结果(或超时错误)通过预设的回调机制或另一个队列发送回最初发起请求的线程。

优点:

macOS UI 自动化技能包 —— 用 Python 控制你的 Mac
macOS UI 自动化技能包 —— 用 Python 控制你的 Mac

让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限

下载
  • 天然的序列化: 串行端口的访问被强制单线程化,完全避免了并发冲突。
  • 高抽象度: 其他线程无需关心底层的同步细节,只需与队列交互。
  • 可扩展性: 易于添加更复杂的协议逻辑(如重试机制、错误处理、数据解析等)。

这种方案将串行端口的竞争完全隐藏在队列机制之后,提供了一种简洁且健壮的抽象。

解决方案二:基于互斥锁(Mutex)的独占访问

另一种直接但同样有效的方法是使用互斥锁(Mutex)来保护对串行端口的访问。这种方法不引入额外的通信线程,而是让所有需要访问串行端口的线程在执行I/O操作前,先获取一个共享的互斥锁。

工作原理:

  1. 共享资源: 串行端口的文件描述符(serial_fd)和互斥锁被视为全局或共享资源。
  2. 获取锁: 任何线程在开始向串行端口写入或读取之前,必须先获取互斥锁。如果锁已被其他线程持有,当前线程将被阻塞,直到锁被释放。
  3. 执行完整周期: 获取锁后,线程执行完整的请求-响应周期(发送请求,然后等待并读取响应)。
  4. 释放锁: 完整的请求-响应周期完成后,线程必须立即释放互斥锁,以便其他等待的线程可以继续。

以下是使用伪代码实现的示例:

// 假设 serial_fd 是串行端口的文件描述符,
// serial_mutex 是保护它的互斥锁
// acquire_the_mutex() 和 release_the_mutex() 是互斥锁操作函数

procedure serial_messaging(u8 *request_mesg, int rqlen, u8 *response_mesg, int rslen)
{
    int rc;

    // 1. 获取互斥锁,确保独占访问
    acquire_the_mutex();    /* 否则调用线程将被阻塞 */

    // 2. 发送请求消息
    rc = write(serial_fd, request_mesg, rqlen);
    if (rc < 0) {
        // 处理错误条件
        release_the_mutex(); // 错误时也需释放锁
        return;
    }
    // tcdrain(serial_fd); // 对于测量超时可能需要,确保所有数据已从输出缓冲区发送

    // 3. 读取响应消息(使用阻塞模式等待)
    rc = read(serial_fd, response_mesg, rslen);
    if (rc < 0) {
        // 处理错误条件
        release_the_mutex(); // 错误时也需释放锁
        return;
    }

    // 4. 释放互斥锁,允许其他线程使用串行端口
    release_the_mutex();    /* 让另一个线程使用串行端口 */
    return;                 /* 返回接收到的数据 */
}

有了 serial_messaging 函数,各个应用线程就可以安全地调用它来发送“foo”或“bar”查询,而无需担心底层的并发问题。例如,在Python中,可以利用 threading.Lock 实现:

import threading
import time
import random

# 模拟串口对象和互斥锁
class MockSerialPort:
    def __init__(self):
        self.last_query = b""

    def write(self, data):
        self.last_query = data # 模拟设备知道最后查询了什么
        print(f"[{threading.current_thread().name}] Sending: {data.decode()}")
        time.sleep(0.05) # 模拟发送时间

    def read(self, length):
        response = f"response_to_{self.last_query.decode()}".encode()
        print(f"[{threading.current_thread().name}] Receiving: {response.decode()}")
        time.sleep(0.05) # 模拟接收时间
        return response[:length] # 模拟响应长度限制

serial_port = MockSerialPort()
serial_lock = threading.Lock()

def get_with_lock(query):
    with serial_lock: # 使用 with 语句自动管理锁的获取和释放
        serial_port.write(query)
        data = serial_port.read(8) # 假设响应长度为8
        return data

def thread1_task():
    while True:
        response = get_with_lock(b"foo")
        print(f"[{threading.current_thread().name}] received: {response.decode()}")
        time.sleep(1)

def thread2_task():
    time.sleep(random.random()) # 随机等待一段时间
    response = get_with_lock(b"bar")
    print(f"[{threading.current_thread().name}] received: {response.decode()}")

# 示例:启动两个线程
# threading.Thread(target=thread1_task, name="Thread-Foo").start()
# threading.Thread(target=thread2_task, name="Thread-Bar").start()

常见误区与重要考量

在实现串行通信的并发抽象时,有几个常见的误区和重要考量需要注意:

  1. 位/字节级数据混合的误解: 许多人担心多个线程同时写入会导致数据在位或字节级别上混淆。实际上,操作系统内核的串行端口驱动程序会处理I/O请求的排队和序列化,因此通常不会发生位或字节级别的混淆。真正的问题在于,如果没有同步机制,高层应用程序无法确保在发送新请求之前,前一个请求的完整响应已经接收完毕,从而导致协议违规和逻辑错误。
  2. 强制执行请求-响应协议: 无论采用哪种方法,核心都是要确保在发送下一个请求之前,前一个请求的完整响应(或超时)已经完成。这意味着 write 操作必须紧跟着相应的 read 操作,并且整个 write-read 周期必须是原子的,不受其他线程干扰。
  3. 超时处理: 在 read 操作中引入超时机制至关重要。如果设备没有在预期时间内响应,read 操作应该超时并返回错误,而不是无限期阻塞。这有助于提高系统的鲁棒性,防止线程因设备无响应而永久挂起。
  4. 选择合适的方案:
    • 专用线程+队列 方案更适用于复杂协议、需要后台持续轮询或事件驱动的场景。它提供了更强的隔离和更高的抽象层次,使得业务逻辑线程更加简洁。但它引入了额外的线程间通信开销。
    • 互斥锁 方案更直接,适用于请求频率不高、协议相对简单的

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

码上飞
码上飞 Hot

一款基于自然语言的AI应用开发平台,可根据用户描述自动生成应用程序,帮助个人和团队快速完成应用原型与开发。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4618

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

1289

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

443

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

579

2026.01.21

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

429

2026.02.06

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

273

2026.03.16

Java 多线程与并发编程实战
Java 多线程与并发编程实战

深入讲解 Java 并发编程体系,涵盖 Thread / Runnable / Callable 线程创建方式、线程生命周期与状态转换、synchronized 同步锁与 Lock/ReentrantLock 显式锁、volatile 可见性保证、ThreadPool 线程池配置与调优(核心参数/拒绝策略)、JUC 并发工具类(CountDownLatch/CyclicBarrier/Semaphore/ConcurrentHashMa

389

2026.04.17

C++ 多线程使用教程大全
C++ 多线程使用教程大全

系统讲解 C++11 及后续标准引入的多线程编程能力,涵盖 std::thread 线程创建与生命周期管理、std::mutex / shared_mutex 互斥锁与读写锁、std::condition_variable 条件变量同步、std::atomic 原子操作与内存序(memory_order)、std::future / promise / async 异步任务、线程池的设计与实现、死锁检测与避免策略,帮助开发者编写正确高效

236

2026.04.28

Docker Desktop怎么设置端口映射
Docker Desktop怎么设置端口映射

本专题聚焦Docker Desktop容器端口映射与访问配置,整理从创建容器、设置端口绑定到验证本地服务访问的操作流程。针对端口被占用、映射配置错误、容器服务无法访问等情况,也提供相应的检查方向,方便用户完成端口配置。

0

2026.11.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn