【问题标题】:std::list threading push_back, front, pop_frontstd::list 线程 push_back、front、pop_front
【发布时间】:2010-12-23 01:10:00
【问题描述】:

std::list 线程安全吗?我假设它不是,所以我添加了我自己的同步机制(我认为我有正确的术语)。但我仍然遇到问题

每个函数都由一个单独的线程调用。 Thread1 等不及了,它必须尽可能快

std::list<CFoo> g_buffer; 
bool g_buffer_lock; 

void thread1( CFoo frame ) {
    g_buffer_lock = true ; 
    g_buffer.push_back( frame ) ; 
    g_buffer_lock = false; 
}


void thread2( )
{
    while( g_buffer_lock ) {
        // Wait 
    }

    // CMSTP_Send_Frame * pMSTPFrame = NULL ; 
    while ( ! g_buffer_lock && g_buffer.size() > 0 )
    {
        // Get the top item 
        CFoo& pFoo = g_buffer.front() ;

        // Do something. 

        // remove the front item 
        g_buffer.pop_front();
    }
}

在大约 170k 次线程 1 调用和 900k 次线程 2 调用后,CFoo&amp; pFoo = g_buffer.front() ; 出现异常错误

这会导致程序崩溃。标准抛出.cpp: 22

#ifdef _DEBUG
_CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _Debug_message(const wchar_t *message, const wchar_t *file, unsigned int line)
    {   // report error and die
        if(::_CrtDbgReportW(_CRT_ASSERT, file, line, NULL, message)==1)
        {
            ::_CrtDbgBreak();
        }
    }
_CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _Debug_message(const unsigned short *message, const unsigned short *file, unsigned int line)
    {   // report error and die
        _Debug_message((wchar_t *) message, (wchar_t *) file, line);
    }

#endif

建议,cmets,有没有更好的做事方式?

【问题讨论】:

  • std::list 线程安全吗?没有。

标签: c++ stl multithreading


【解决方案1】:

不,不能保证线程安全。

您的同步机制有缺陷。您允许thread1 更改列表,而thread2 正在使用它。这可能会导致问题。除此之外,你应该让你的锁变量volatile

【讨论】:

  • 为这两个函数添加了等待检查。运行测试以查看它是否可以解决此问题。我会想到,因为 thread1 只添加项目,而 thread2 只删除不会导致任何问题的项目。但看起来确实如此。
  • @Steven:正如 sbi 在他的回答中指出的那样,您最好使用系统提供的锁定机制,而不是自己滚动。
【解决方案2】:

std::list 线程安全吗?

当前的 C++ 标准甚至不承认线程的存在,所以std::list 肯定不是。然而,不同的实现可能会提供(不同级别的)线程安全。

至于您的密码:如果您需要锁,请使用锁。当线程在从不同缓存中获取它的不同内核上执行时,bool 变量可能无济于事。请改用真正的互斥锁。

【讨论】:

    【解决方案3】:

    您认为 stl 列表不能保证是线程安全的,这是正确的。

    而且你的同步机制可能不是很好。

    在 thread2 中,你没有锁定你的布尔值,所以你有问题。

    您应该至少在您的 lock bool 前面放置一个 volatile 限定符;或者最好还是研究适合您平台的真正互斥函数。

    【讨论】:

    • volatile 不能替代内存屏障,并且在需要真正的内存屏障时不起作用。
    【解决方案4】:

    仅仅因为 Thread1 需要尽可能快,并不意味着它可以在列表被另一个线程更改时让它访问它。由于两个线程都在修改列表,因此都必须等待。如果您最终得到损坏的数据,那么快速也无济于事。

    编辑:

    实际上你可以侥幸逃脱... Thread1 只将元素添加到列表中,而 Thread2 只删除它们。这意味着如果列表只包含一个元素,Thread1 只需要等待。

    编辑2:

    因此,实现这项工作的方法是,如果列表仅包含一个元素,则 thread2 将其锁定。它必须在每次删除之前检查。这样,thread1 就不必等待,除非是那种情况。

    您绝对应该使用适当的互斥机制(无论您的平台上是否可用)而不是布尔标志。

    【讨论】:

    • 这也是我的想法,因为 thread1 添加和 thread2 删除了我可以同时运行它们的项目......但实际上似乎并非如此。
    • 那是因为你不知道 thread1 尝试添加时 thread2 删除了多少元素...
    • 我想我明白了。 thread2 仅应在即将删除最后一个元素时锁定列表。这样,thread1 将永远不必等待,除非是那种情况。
    【解决方案5】:

    如果您确实需要线程 1 尽可能快,但仍需要线程安全,则可以以最小的开销为代价来防止某些锁争用,例如:

    std::list<CFoo> g_buffer_thread1;
    std::list<CFoo> g_buffer_thread2;
    Mutex g_mutex; 
    
    void thread1( CFoo frame ) {
        Locker lock( g_mutex );
        g_buffer_thread1.push_back( frame ) ; 
    }
    
    
    void thread2( )
    {
        while( g_buffer_thread2.size() == 0 ) {
            // Wait?
            Locker lock( g_mutex );
            g_buffer_thread1.swap( g_buffer_thread2 );
        }
    
        while ( g_buffer_thread2.size() > 0 )
        {
            CFoo& pFoo = g_buffer_thread2.front() ;
            // Do something.
            g_buffer_thread2.pop_front();
        }
    }
    

    我认为这是与线程安全最直接的组合。不幸的是,Thread1 必须始终锁定。您可能会想出一些方法来为 thread1 批处理帧。根据您在问题中的数字,我假设线程 1 运行的次数比线程 2 运行的次数多,因此这将节省一些锁定争用,否则仅使用一个缓冲区列表就会发生这种争用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-02
      • 1970-01-01
      相关资源
      最近更新 更多