我应该在多线程编程中始终锁定全局数据吗?为什么

should i always lock the global data in multi-thread programming, why or why not?

本文关键字:数据 全局 为什么 锁定 多线程 编程 我应该      更新时间:2023-10-16

我是多线程编程的新手(实际上,我不是多线程领域的新人,但我总是使用全局数据来读写线程,我认为这会让我的代码变得丑陋和缓慢,我渴望提高我的技能)

我现在正在使用c++开发一个转发器服务器,为了简化问题,我们假设只有两个线程,一个接收线程和一个发送线程,而且,像往常一样愚蠢的设计,我有一个全局std::列表来保存数据:(

receiving-thread从服务器读取原始数据并将其写入全局std::list。

sending-thread读取全局std::list并将其发送给多个客户端。

我使用pthread_mutex_lock来同步全局std::列表。

问题是转发服务器的性能很差,在写入receiving-thread时全局列表被锁定,但当时我的sending-thread想读取,所以它必须等待,但我认为这种等待是无用的。

我该怎么办,我知道全局性很差,但是,如果没有全局性,我如何同步这两个线程?

我会继续从SO和谷歌上搜索。

任何建议、指南、技术或书籍都将不胜感激。谢谢

编辑

  1. 对于任何建议,我想知道为什么或为什么不,请告诉我原因,非常感谢

备注:

  1. 请提供更完整的示例:http://sscce.org/

答案:

  1. 是的,您应该同步对共享数据的访问。

    • 注意:这对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
  2. 不要在C++中使用pthread_*,除非你真的知道自己在做什么-使用std::thread(C++11)或boost::thread-或者自己在类中包装pthread_*-因为如果你不考虑异常,你最终会出现死锁

  3. 在这个特定的例子中,你无法通过某种形式的同步,但你可以优化同步

    1. 不要将数据本身复制到std::list中或从中复制出来-将指向数据的指针复制到列表中或从列表中复制出来
    2. 只在实际访问std::列表时锁定,但不要犯这个错误:

      {
          // lock
          size_t i = g_list.size();
          // unlock
          if ( i )
          {
              // lock
              // work with g_list ...
              // unlock
          }
      }
      
  4. 这里更合适的模式是消息队列——您可以使用互斥、列表和条件变量来实现消息队列。以下是一些您可以查看的实现:

    • 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
    • 谷歌了解更多
  5. 还有原子容器的选项,请看:

    • http://calumgrant.net/atomic/-不确定这是否由实际的原子存储支持(而不是仅在接口后面使用同步)
    • 谷歌了解更多
  6. 您也可以选择带有boost::asio的异步方法——尽管如果做得好,您的案例应该会很快。