C++并发,同步设计,避免多次执行问题

Posted

技术标签:

【中文标题】C++并发,同步设计,避免多次执行问题【英文标题】:C++ concurrency, synchronization design to avoid multiple execution issue 【发布时间】:2013-04-02 06:11:20 【问题描述】:

我的代码调用了来自 3rd 方库的函数。我们称这个函数为SomeFunc

void SomeFunc(void (*callBack) (int));

如您所见,SomeFunc 采用回调函数参数。一旦 SomeFunc 被调用,调用线程将继续进行,并且库将在不同的线程上多次执行回调——传递不同的状态代码。

我的要求是调用 SomeFunc 的线程(又名主线程)应该等到某些状态代码被传递给回调。到目前为止,我有这样的东西

CEvent *pEvt = NULL;

void myCallBack(int code) 
  if(code == SOME_MAGIC_NUM) pEvt->SetEvent(); // signal thread waiting for this event obj they can continue


int main (int argc, char** argv) 
  pEvt = new CEvent(FALSE, TRUE);
  SomeFunc(myCallBack); // This doesn't block, main thread will progress to next line
  WaitForSingleObject(pEvt, 5000); // wait here until 3rd party library call myCallBack with code SOME_MAGIC_NUM -- or if it doesn't after 5 seconds, continue

  // do interesting stuff here..

  return EXIT_SUCCESS;

如果我只在上面的主线程/主函数上执行此操作,这似乎很好。但是,如果多个线程可以执行上面 main 中的代码块,我的问题是它们将共享对全局 pEvt 变量的引用,它会搞砸

我应该在这里采用的最佳代码设计方法是什么?理想情况下,我想更改回调函数签名以接受对 CEvent 对象的引用,但由于它是第 3 方库,我无法这样做。

【问题讨论】:

目前还不清楚:SomeFunc() 是否产生了您的辅助线程,然后返回给它的调用者(在本例中为 main())?我之所以问,是因为您在多线程上下文中提出了这个问题,然后继续显示完全没有多线程的代码。 正确,SomeFunc() 产生了辅助线程——我们称之为“线程 X”。从 Thread X 它将调用 myCallBack 并传递不同的代码参数。主线程和线程 X 将同时执行,这就是为什么我在上面使用WaitForSingleObject(pEvt, 5000) 来暂停主线程,直到我乐于进行 请问为什么需要多个线程来监控单个回调事件? 无论如何,你在后续代码中引入了竞争条件。正如所写,不能保证main() 不会在实现 SomeFunc() 的库管理的线程完成之前完成。此外,5000是因为……? 4999 还不够,5001 太多了?您描述代码中等待的评论不准确。它应该是:“在这里等到第 3 方库调用 myCallBack,代码为 SOME_MAGIC_NUM,或者我只是在大约 5 秒后不在乎。”但我假设你确实在乎. @jwalk,代码是 Web 服务实现的一部分。每次客户端发出请求时,都会在单独的工作线程上提供响应,但恐怕全局变量 pEvt 会在所有工作线程上共享。如果 2 个客户端同时调用 Web 服务,就会出现并发问题。我考虑过将类成员作为回调传递,但 C++ 有不同的函数指针类型,它不会编译。 【参考方案1】:

你真的想要 javascript 中的闭包。您可以通过每次将函数调用绑定到新的 CEvent 对象来完成类似的操作。看看std::bind1st 或boost::bind。

另请参阅*** thread

【讨论】:

【参考方案2】:

只有第 3 方提供了一种将“自定义”参数传回回调的方法,才能实现这一点。精心设计的 API 允许设置回调一个 void* 值,并且回调在调用时接收此参数。从这个 void* 你可以通过不安全的转换扩展到任何你喜欢的东西,包括对象和方法调用。所有基于绑定或成员函数地址或其他任何东西的解决方案最终都归结为同一个问题:必须以某种方式将this* 传递回回调。

例如,参见BIO_set_callback:它允许您设置回调一个回调任意参数。请注意,callabck 参数可以是 indirect,例如在 gnutls 中:可以通过 gnutls_session_set_ptr 将参数设置为会话中的任意数据,然后在回调中使用gnutls_session_get_ptr。您的第 3 方可能会提供这种间接方法。

如果库不提供此类功能,那么您将陷入黑客攻击。例如,您可以拥有一组“可用”回调,每个回调都与特定事件相关联(即,不同函数作为地址,尽管代码相同)。您选择一个回调并将其从集合中移除并放置,然后等待与 that 回调关联的事件。完成后,将回调放回可用列表中。 “可用”列表的大小在编译时是硬编码的,因为您确实需要单独的函数,每个回调一个。

【讨论】:

不正确。这就是绑定的用途。 @hifier:请告诉绑定如何接收隐藏的this* 参数? like this. 这会将this* 作为副本一直传递到调用。 仅适用于 C++。显示带有签名void f(int) 的普通 C 回调示例,如 OP 中所示。 ... by "hidden this* argument" 我假设您在问如何将仿函数绑定到类的特定实例上的方法。

以上是关于C++并发,同步设计,避免多次执行问题的主要内容,如果未能解决你的问题,请参考以下文章

Linux C++多线程同步的四种方式

操作系统学习---进程管理

大并发热点行更新的两个骚操作

linux内核同步问题

实战JAVA 高并发设计

使用JDK的同步容器时,应该避免那些坑?