混合 C 和 C++ 的线程同步

Posted

技术标签:

【中文标题】混合 C 和 C++ 的线程同步【英文标题】:Thread synchronization with mixed C and C++ 【发布时间】:2014-01-26 05:53:31 【问题描述】:

我有一个多线程程序,主线程是第三方(无法更改)和纯 C。我的任务是围绕它构建新模块(在 C++ 中),这些模块部分驻留在其他线程中并且需要使用 C 程序的接口。基本上只是读取一些在 C 线程中存储和更新的变量(整数、浮点数,没什么复杂的)。

现在我的问题是:我如何确保在访问这些变量时不会从 C 接口中得到垃圾,因为我不能在读取时使用互斥锁来锁定它。这甚至可能吗?还是写一个浮点/整数是原子操作?

【问题讨论】:

混合 C/C++ 与问题关系不大。我建议将此线程称为“与外部库的数据同步”或类似的东西。 什么CPU和操作系统?我认为这将对什么是原子的,什么不是原子的产生影响。此外,您总是会遇到缓存问题,即其他线程已更改该值但尚未将其写入主 RAM,因此您最好不要做任何具有稍微过时的值是致命问题的事情。 我就是这么想的……我不想让它依赖于一些特殊的架构。 @fewu - 您能否详细介绍一下“主线程”的结构和问题的 C 部分,以及 C++ 程序将如何与之交互?您提到主线程将是 C,您无法更改它,但这提出了一个问题 - C++ 线程首先是如何启动的?另外(这确实应该是第一个问题)- C 代码本身记录了线程安全性是什么?诚然,答案可能是“没什么”,但至少值得一试。 主线程是 C(实际上是从 Matlab Simulink 生成的代码),具有我在 C++ 中实现的定义接口,因为我必须连接另一个 C++ 库(也是第 3 方)。在这个 C++ 实现中,我启动了 C++ 线程。关于您的第二个问题:正如您所怀疑的那样,线程安全与此无关。 【参考方案1】:

你不能。读写任何东西都不是原子操作,如果你不能改变 C 代码,那你就倒霉了。同步总是需要两个部分进行同步。

最好的办法是要求第三方确保他们的部分线程安全和/或与您共享锁定机制。

【讨论】:

您通常可以在自己的调用代码中进行所有锁定 - 保护对未知代码的每次调用,因此 C 库中一次只有一个线程。由于这有效地呈现了对 C 库的单线程访问,因此几乎可以保证它可以工作。自从有了糟糕的原生库以来,这种技术就被用于与糟糕的原生库进行交互。 我想这就是我要做的。要求第 3 方创建一个类似WriteNewValue(int newVal) 的接口,他们每次更新值时都会调用该接口,以便我可以将变量复制到我可以锁定/解锁访问的 C++ 世界中。【参考方案2】:

你不能。在这种情况下工作的唯一正确方法是仅使用调用 C 线程提供给您的函数的参数 - 之后不要存储对它们的任何引用。无法保证任何变量都不会被修改 - 一般情况下。

您需要重新考虑您的架构,以免出现这种需求。

【讨论】:

【参考方案3】:

如果您无法确保设置变量值的代码是同步的,那么在读取时加锁是没有意义的并且不会起作用。这不仅是操作的原子性,也是数据的可见性 - 对这些变量的更新可能对其他线程不可见。

如果您控制主线程,则必须为每个必须访问的变量创建一个新变量,从主线程访问它,并使用锁,设置新创建变量的值。然后,从其他线程,只访问那些同步变量。

int myVal = 0;

int main() 
  while(!shouldQuit()) 
    doSomeIndependentStuff();
    pthread_lock(&mutex);
    myVal = independentGlobalVal;
    pthread_unlock(&mutex);
  


int getMyVal() 
  int retVal = 0;
  pthread_lock(&mutex);
  retVal  = myVal;
  pthread_unlock(&mutex);
  return retval;

【讨论】:

【参考方案4】:

不幸的是,像“写一个 float/int [is] an atomic operation”之类的语句在 C 或 C++ 中没有得到很好的定义(尽管在 C++11 和 stdatomic.h 方法中使用了std::atomic来自 C11 的内容在这里可以提供帮助 - 但这对您无法修改的库的 C 互操作没有帮助,因此您可以在这里忽略它。

您可以在特定编译器和平台上找到有关这些问题的指南 - 例如,您可能会发现,在大多数平台上,对齐的 32 位或 64 位读取或写入将是原子的(如果对齐),并且大多数编译器会适当地对齐它们。

然而,这条路上充满了疯狂。如果您涉及多个线程,只需使用 POSIX/pthreads 功能,例如 pthreads mutexes - 可以从 C 和 C++ 轻松访问,以保护对跨线程共享状态的任何访问。

由于您无法修改 C 代码,您可能必须在 C++ 代码中进行所有锁定,然后再调用 C 库,然后解锁。如果您可以阅读但不能修改 C 代码,或者文档对线程/共享模型非常清楚,您也许可以使用细粒度锁定策略,但在没有任何分析表明存在瓶颈的情况下,我d 从一个全局锁开始,用于保护对 C API 的每次访问。

【讨论】:

如果写入不同步,则只锁定读取是没有意义的。 c11 也具有原子性。标准原子.h 我从不建议只锁定读取。我建议锁定对 C 库的任何调用,这将包含所有读取和写入(通过本机库)。如果有任何实际的共享状态(例如,共享库公开的全局变量),您需要在访问它时在 C++ 端进行锁定。 @Dariusz - 在更仔细地阅读了 OP 的问题描述之后,您似乎有道理 - 他暗示“主线程”归 C 代码所有,并将完全保留在 C 代码中,这会使事情复杂化。这在设计上似乎不太可能 - C++ 线程是如何被启动的,并且是嵌入 C 库的通常模型的反转,但我会要求 OP 进行澄清。 @BeeOnRope 我们有。由于原始 C 代码不是多线程的,它将调用新模块并期望答案作为调用的返回值。然后,您需要在调用堆栈的下方与原始 C API 进行交互。当您在通话中时,您可以简单地使用所描述的经典锁定。但是一旦你出去了,你基本上必须等待下一次调用你的模块。但是从本质上讲,如果您正确设计模块并且不破坏原始程序设计假设,您可能不需要这样做。

以上是关于混合 C 和 C++ 的线程同步的主要内容,如果未能解决你的问题,请参考以下文章

c++多线程同步

第三十章 混合线程同步构造

用c语言或c++编写编程实现生产者消费者或读写者的同步问题

简单的多线程帮助? C++、WaitForSingleObject 和同步

将一个线程与两个正在运行的线程同步,c++,windows

没有多线程的 C++ 套接字非同步/并行代码