0

0

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

DDD

DDD

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

|

872人浏览过

|

来源于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. 响应回传: 收到响应后,主通信线程将结果(或超时错误)通过预设的回调机制或另一个队列发送回最初发起请求的线程。

优点:

标小智
标小智

智能LOGO设计生成器

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

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

解决方案二:基于互斥锁(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

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

786

2023.08.10

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

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

378

2025.12.24

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

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

33

2026.01.21

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

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

31

2026.01.21

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

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

105

2026.02.06

linux是嵌入式系统吗
linux是嵌入式系统吗

linux是嵌入式系统,是一种用途广泛的系统软件,其特点是:1、linux系统是完全开放、免费的;2、linux操作系统的显著优势是多用户和多任务,保证了多个用户使用互不影响;3、设备是独立的,只要安装驱动程序,任何用户都可以对任意设备进行使用和操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

175

2024.02.23

C++ 嵌入式系统开发入门与实践
C++ 嵌入式系统开发入门与实践

本专题将带你系统掌握 C++ 在嵌入式系统中的实战应用,内容覆盖硬件抽象、驱动开发、内存与性能优化、实时系统编程、跨平台编译构建,以及常用嵌入式框架与调试技巧,帮助开发者从零构建可运行于 MCU、ARM 等平台的高性能嵌入式项目。

228

2025.11.18

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

76

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

116

2026.03.12

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 2万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号