在线程之间共享通用数据

时间:2014-05-13 23:14:57

标签: c multithreading

我需要为客户端服务器交互创建一个库。数据请求不常见,大多只有几个字节(整数,长整数)。用户定义结构的请求很少。我已经查看了一些类似的问题(thesethese),但没有找到我在这里提出的问题的答案,因为我主要关注的是数据是通用的。

我的设计问题是线程之间的数据共享: 我的库有2个线程(pthreads),其中一个是msg处理程序(事件循环)。 msg处理程序循环从服务器获取响应,并需要将数据传递给另一个线程。数据是通用的 - 因为可以是用户定义的struct或int或long。此外,可以多次接收相同类型的响应(每个响应都是唯一标识的),这意味着每种数据类型的单个全局变量不会这样做。另一个线程正在等待一个条件变量,并将被msg处理程序线程唤醒。条件变量不在布尔变量上,而是检查msg处理程序是否已将请求的信息添加到某个“已知位置”。那么,如何在线程之间实现这种数据共享呢?

我考虑了以下一些方法:

  1. 全局通用列表 - 带有void * data ...处理程序线程在收到服务器的响应时分配内存并发出另一个线程的信号。第一个线程唤醒并遍历列表以查找特定的id。找到后,使用数据然后释放内存。

    没有必要定义不同的msg类型(对于int,struct,long等),但是walk会比较长。

  2. 全局列表数组 - 每种数据类型的列表头指针数组。分配和免费与上述相同。除此之外,数组按数据类型(枚举)索引。

    步行相对较短,但需要为每个列表定义不同的msg_types。

  3. 我还关心动态分配像int这样导致碎片的小数据。所以尝试分配一个没有动态分配但在语义上没有成功的数组。 (找不到足够复杂的例子)

    如果这是正确的方法,我会投入更多时间。 (一个例子会有所帮助!)

  4. 用于生成每种类型struct / msg的宏...刚刚决定这是一种矫枉过正(在示例中看起来非常复杂)

  5. 共享内存 - 使用char *缓冲区进行分配似乎太尴尬..

  6. 在这种情况下,最好的设计是什么?我希望这是可以理解的。

2 个答案:

答案 0 :(得分:1)

这是一个生产者 - 消费者场景,我的首选是实现一个阻塞队列。

事件处理程序将消息推送到队列中。工作线程在空时阻塞队列,并在有待处理的事项时唤醒。

可以分配和释放消息,但如果从空闲列表中提取消息,通常会更好。消息只是类型的联合,具有类型字段和共同的id。好又简单。

扩展到多个工作线程和/或新消息类型非常简单。

您可以使用您选择的语言轻松找到此类阻止队列的预编写代码。

[您的约束条件可能不符合此方法,但如果是这样,请编辑您的问题以明确说明。]


C ++中的可变长度阻塞队列有三种基本策略。假设消息是不同类型的不同大小的并集。

  1. 队列中的消息大小固定,并包含指向消息的指针。根据需要分配和释放每条消息。
  2. 队列中的消息大小固定,并包含指向消息的指针。消息保存在池(空闲列表/数组)中,以避免分配开销。
  3. 队列中的消息大小可变。它们包含一个压入队列的字节流,其长度可以知道弹出多少。
  4. 通过搜索"阻塞队列变量长度C ++"您可以轻松找到示例代码和完整的工作示例。或类似的。

答案 1 :(得分:0)

  

我的图书馆有两个帖子

     

数据请求不常见

这些是决定因素:它意味着您不会遇到与吞吐量相关的问题,例如内存分配压力,并且您不需要复杂的信号系统。

  1. 您可以使用简单的列表来存储您的项目,并根据需要使用malloc / free结构
  2. 只有两个线程,它是1个生产者/ 1个消费者设置,这意味着你可以简单地使用列表中的保护单元来有效地分割消费者和生产者之间的使用,而不必互斥它。
  3. 但是,如果您认为这些因素将来可能会发生变化,您需要确保访问您的列表数据结构的代码能够很好地描述其余部分(即制作API),为您留下空间。稍后修改该列表用法(例如,通过包含互斥锁,或包括消息密钥索引)。

    关于数据结构:您需要代码知道它必须处理哪种类型的数据,这意味着传递的数据必须包含类型标记。最简单的方法是创建一个您必须处理的所有数据类型的联合类型,并在其前面添加一个标记数据的枚举值:

    typedef enum {
       TYPE1,
       TYPE2,
       TYPE3
       // etc.
    } data_tag;
    
    typedef union {
       struct type1 {/* .. */};
       struct type2 {/* .. */};
       struct type3 {/* .. */};
       // etc.
    } data_content;
    
    
    typedef struct {
       data_tag tag;
       data_content value;
    } message;
    

    然后,在您的代码中,只需在标记字段上使用switch来区分用例。

    内存处理的一种可能解决方案是使用所谓的memory pools而不是简单的malloc方案。基本上,您事先为每个data_content子类型分配了许多实例,并围绕它们构建消息。

    typedef struct {
       data_tag tag;
       data_content *value;
    };
    

    如果你小心(即你正确地构造你的值,并适当地打开它们),你实际上指向一个数据类型而不是一个联合的事实应该无关紧要,并且应该让你避免混乱你的具有许多类型转换的代码。

    对于较小的内存压力,您可能不需要使其太复杂,但您可以添加一种机制来扩展这些池以满足强烈需求,将它们实现为结构数组列表。

    如果您真的关心代码复杂性,请考虑使用支持内置内存回忆的语言(例如,Erlang,OCaml,Go,Python等)。此外,许多函数语言(如OCaml)支持所谓的algebraic datatypes,可以轻松地在语言中实现上述消息类型。