我应该在多线程编程中始终锁定全局数据吗?为什么
should i always lock the global data in multi-thread programming, why or why not?
我是多线程编程的新手(实际上,我不是多线程领域的新人,但我总是使用全局数据来读写线程,我认为这会让我的代码变得丑陋和缓慢,我渴望提高我的技能)
我现在正在使用c++开发一个转发器服务器,为了简化问题,我们假设只有两个线程,一个接收线程和一个发送线程,而且,像往常一样愚蠢的设计,我有一个全局std::列表来保存数据:(
receiving-thread
从服务器读取原始数据并将其写入全局std::list。
sending-thread
读取全局std::list并将其发送给多个客户端。
我使用pthread_mutex_lock
来同步全局std::列表。
问题是转发服务器的性能很差,在写入receiving-thread
时全局列表被锁定,但当时我的sending-thread
想读取,所以它必须等待,但我认为这种等待是无用的。
我该怎么办,我知道全局性很差,但是,如果没有全局性,我如何同步这两个线程?
我会继续从SO和谷歌上搜索。
任何建议、指南、技术或书籍都将不胜感激。谢谢
编辑
- 对于任何建议,我想知道为什么或为什么不,请告诉我原因,非常感谢
备注:
- 请提供更完整的示例:http://sscce.org/
答案:
-
是的,您应该同步对共享数据的访问。
- 注意:这对std::list实现进行了假设,这可能适用于也可能不适用于您的案例,但由于这些假设对某些实现有效,如果没有明确的保证,您就不能假设您的实现必须是线程安全的
-
考虑一下这个片段:
std::list g_list; void thread1() { while( /*input ok*/ ) { /*read input*/ g_list.push_back( /*something*/ ); } } void thread2() { while( /*something*/ ) { /*pop from list*/ data x = g_list.front(); g_list.pop_front(); } }
- 例如,列表中有一个元素
- std::list::push_back()必须执行以下操作:
- 分配空间(许多CPU指令)
- 将数据复制到新空间(许多CPU指令)
- 更新上一个元素(如果存在)以指向新元素
- 设置std::list::_size
- std::list::pop_front()必须执行:
- 自由空间
- 将下一个元素更新为没有上一个元素
- 设置std::listrongize
- 现在假设线程1调用push_back()-在检查是否有元素后(检查大小)-它会继续更新这个元素-但就在这之后-在它有机会更新元素之前-线程2可能正在运行pop_front-并忙于释放第一个元素的内存-这可能会导致线程1导致分段错误,甚至内存损坏。类似地,对大小的更新可能会导致push_back战胜pop_front的更新,然后当你只有1个元素时,你就有了大小2
-
不要在C++中使用pthread_*,除非你真的知道自己在做什么-使用std::thread(C++11)或boost::thread-或者自己在类中包装pthread_*-因为如果你不考虑异常,你最终会出现死锁
-
在这个特定的例子中,你无法通过某种形式的同步,但你可以优化同步
- 不要将数据本身复制到std::list中或从中复制出来-将指向数据的指针复制到列表中或从列表中复制出来
-
只在实际访问std::列表时锁定,但不要犯这个错误:
{ // lock size_t i = g_list.size(); // unlock if ( i ) { // lock // work with g_list ... // unlock } }
-
这里更合适的模式是消息队列——您可以使用互斥、列表和条件变量来实现消息队列。以下是一些您可以查看的实现:
- http://pocoproject.org/docs/Poco.Notification.html
- http://gnodebian.blogspot.com.es/2013/07/a-thread-safe-asynchronous-queue-in-c11.html
- http://docs.wxwidgets.org/trunk/classwx_message_queue_3_01_t_01_4.html
- 谷歌了解更多
-
还有原子容器的选项,请看:
- http://calumgrant.net/atomic/-不确定这是否由实际的原子存储支持(而不是仅在接口后面使用同步)
- 谷歌了解更多
-
您也可以选择带有boost::asio的异步方法——尽管如果做得好,您的案例应该会很快。
- 将线程中的数据存储到全局容器的最佳方法?
- 带参数的数据结构的全局声明
- 剪贴板数据上使用的全局大小函数会导致错误
- 从全局 CUDA 函数返回数据
- 使用全局命名空间限定符和指向数据成员的指针
- 访问全局数据结构
- 在不使用For循环的情况下将数据从指向本地数组的指针传输到全局数组
- 使用函数调用初始化静态全局数据(在编译时)
- 全局声明的数据结构可以有多大
- 包含数据的全局静态类
- c++全局函数与根据内存的数据成员函数
- 将游戏数据加载到没有全局变量的池中
- (威纳皮C++)如何在没有全局变量的情况下将数据从一个窗口传递到另一个窗口
- C++:为什么与访问全局变量相比,访问类数据成员如此缓慢
- 全局访问单例数据
- 当 pthread 启动时,它是否需要互斥锁来访问之前在生成它的线程中写入的全局数据
- 如何定义来自另一个命名空间(而不是全局命名空间)的函数和数据
- 如何在 MPI 中使用共享的全局数据集
- 我应该在多线程编程中始终锁定全局数据吗?为什么
- C++静态,外部使用全局数据