C++互斥锁锁定错误

C++ mutex locking error

本文关键字:锁定 错误 C++      更新时间:2023-10-16

我正在使用一个具有工作窃取功能的线程池,但每当程序试图锁定工作队列中的互斥对象时,我都会收到一个异常错误。

我在Windows Visual Studio 2015和Ubuntu 14.04上都尝试过该程序,但都产生了运行时异常。

我已经对工作队列本身进行了广泛的测试,无法重现错误。如果我注释掉try_steal函数,我不会遇到任何错误。最后,我用std::recursive_mutex替换了std::mutex,但仍然会得到同样的错误。

我认为异常发生在线程池的解构过程中,即一个线程试图读取另一个已经被破坏的线程的工作队列。但是,即使在程序结束前引入了无限循环之后,也会出现同样的异常。

我想知道是否还有其他我不想检查的东西,下面你会发现相关的代码以及VS2015和Linux调用堆栈。

谢谢你的帮助。

Windows调用堆栈:

msvcp140d.dll!mtx_do_lock(_mtx_internalimp_t*mtx,const xtime*target)msvcp140d.dll_Mtx_lock(_Mtx_internal_inmp_t*Mtx)thread_pool_test.exe!std::_Mtx_lockX(_Mtx_internal_inmp_t*_Mtx)thread_pool_test.exe!std::_Mutex_base::lock()thread_pool_test.exe!std::lock_guard::lock_guard(std::mutex&_Mtx)thread_pool_test.exe!work_stealing_queue::try_steal(function_wrapper&res)thread_pool_test.exe!thread_pool_steal::pop_task_from_other_thread_queue(function_wrapper&task)thread_pool_test.exe!thread_pool_steal::run_pending_task()thread_pool_test.exe!thread_pool_steal::worker_thread(未签名的int my_index_)thread_pool_test.exe!std::_Invoker_pmf_pointer::_Call(void(unsigned int)*_pmf,thread_pool_steal*&amp_Arg1,int&amp&lt_Args2_0>)第1373 C行++thread_pool_test.exe!std::invoke(void(unsigned int)*&amp_对象,thread_pool_steal*&amp&lt_Args_0>,int&amp&lt_Args_1>)thread_pool_test.exe!std::_LaunchPad,std::default_delete>>::_Execute<0,1,2>(std::元组&_Tup,std::整数序列__formal)thread_pool_test.exe!std::_LaunchPad,std::default_delete>>::Run(std::.LaunchPad、std::default _delete>>*_Ln)thread_pool_test.exe!std::_LaunchPad,std::default_delete>>::_Go()thread_pool_test.exe!std::_Pad::_Call_func(无效*_Data)ucrtbased.dll!0fa27e48()[下面的帧可能不正确和/或丢失,没有为基于ucrt.dll加载符号]ucrtbased.dll!0fa27b8()kernel32.dll@BaseThreadInitThunk@12()ntdll.dll!___RtlUserThreadStart@8()ntdll.dll!__RtlUserThreadStart@8()

Linux调用堆栈:

[New Thread 0x7ffff6f5d700 (LWP 4395)]
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff6f5d700 (LWP 4395)]
__GI___pthread_mutex_lock (mutex=0x0)
    at ../nptl/pthread_mutex_lock.c:66
66  ../nptl/pthread_mutex_lock.c: No such file or directory.
(gdb) bt
#0  __GI___pthread_mutex_lock (mutex=0x0)
    at ../nptl/pthread_mutex_lock.c:66
#1  0x0000000000401f53 in __gthread_mutex_lock (__mutex=0x50)
    at /usr/include/x86_64-linux-gnu/c++/4.9/bits/gthr-default.h:748
#2  0x00000000004023ba in std::mutex::lock (this=0x50)
    at /usr/include/c++/4.9/mutex:135
#3  0x000000000040370a in std::lock_guard<std::mutex>::lock_guard (
    this=0x7ffff6f5cd10, __m=...) at /usr/include/c++/4.9/mutex:377
#4  0x00000000004030fa in work_stealing_queue::try_steal (this=0x0, 
    res=...) at Source.cpp:250
#5  0x00000000004032c8 in thread_pool_steal::pop_task_from_other_thread_queue (this=0x7fffffffdac0, task=...) at Source.cpp:302
#6  0x00000000004035e4 in thread_pool_steal::run_pending_task (
    this=0x7fffffffdac0) at Source.cpp:358
#7  0x00000000004031ba in thread_pool_steal::worker_thread (
    this=0x7fffffffdac0, my_index_=0) at Source.cpp:283
#8  0x000000000040d3d4 in std::_Mem_fn<void (thread_pool_steal::*)(unsigned int)>::operator()<int, void>(thread_pool_steal*, int&&) const (
    this=0x62af78, __object=0x7fffffffdac0)
    at /usr/include/c++/4.9/functional:569
#9  0x000000000040cec9 in std::_Bind_simple<std::_Mem_fn<void (thread_pool_steal::*)(unsigned int)> (thread_pool_steal*, int)>::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) (this=0x62af68)
    at /usr/include/c++/4.9/functional:1700
#10 0x000000000040c87f in std::_Bind_simple<std::_Mem_fn<void (thread_pool_steal::*)(unsigned int)> (thread_pool_steal*, int)>::operator()() (
    this=0x62af68) at /usr/include/c++/4.9/functional:1688
#11 0x000000000040c4ea in std::thread::_Impl<std::_Bind_simple<std::_Mem_fn<void (thread_pool_steal::*)(unsigned int)> (thread_pool_steal*, int)> >::_M_run() (this=0x62af50) at /usr/include/c++/4.9/thread:115
#12 0x00007ffff78f7e40 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#13 0x00007ffff7bc4182 in start_thread (arg=0x7ffff6f5d700)
    at pthread_create.c:312
#14 0x00007ffff735e47d in clone ()
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

代码:

class work_stealing_queue
{
 private:
 typedef function_wrapper data_type;
 std::deque<data_type> the_queue;
 mutable std::mutex the_mutex;
bool empty() const
{
    std::lock_guard<std::mutex> lock(the_mutex);
    return the_queue.empty();
}
bool try_steal(data_type& res)
{
    std::lock_guard<std::mutex> lock(the_mutex);
    if (the_queue.empty())
    {
        return false;
    }
    res = std::move(the_queue.back());
    the_queue.pop_back();
    return true;
}
};

class thread_pool_steal
{
typedef function_wrapper task_type;
std::atomic_bool done;
threadsafe_queue<task_type> pool_work_queue;
std::vector<std::unique_ptr<work_stealing_queue> > queues;
std::vector<std::thread> threads;
static thread_local work_stealing_queue* local_work_queue;
static thread_local unsigned int my_index;
join_threads joiner;

bool pop_task_from_other_thread_queue(task_type& task)
{
    for (unsigned i = 0; i<queues.size(); ++i)
    {
        unsigned const index = (my_index + i + 1) % queues.size();
        if (queues[index]->try_steal(task))
        {
            return true;
        }
    }
    return false;
}
public:
thread_pool_steal() : done(false), joiner(threads)
{
    unsigned const thread_count = std::thread::hardware_concurrency();
    try
    {
        for (auto i = 0; i<thread_count; ++i)
        {
            queues.push_back(std::unique_ptr<work_stealing_queue>(std::make_unique<work_stealing_queue>()));
            threads.push_back(std::move(std::thread(&thread_pool_steal::worker_thread, this, i)));
        }
    }
    catch (...)
    {
        done = true;
        throw;
    }
};
~thread_pool_steal()
{
    done = true;
};

在Linux中,在调用pthread_mutex_lock()之前,必须初始化"the_mutex"。参见Unix网络编程第1卷第2版(W.Richard Stevens)第626页。它说:"如果互斥变量是静态分配的,我们必须将其初始化为常量PTHREAD_mutex_INITIALIZER。"

pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER;

出现此错误的原因似乎是std::vector不是线程安全的。当主线程添加到work_stealing_queue的向量queues时,新生成的线程调用queues.size()。我猜size()函数在队列中最新的work_steaing_queue准备就绪之前递增。解决方案是创建两个单独的循环,一个用于创建队列中的所有work_stealing_queue,另一个用于使用函数worker_thread运行线程。

for(auto i=0;i<thread_count;++i)
            {
               queues.push_back(std::unique_ptr<work_stealing_queue>  (std::make_unique<work_stealing_queue>()));
            }
for(auto i=0;i<thread_count;++i) // Seperate becuse std::vector is not thread safe and causes issues when size() is used
            {
                threads.push_back(std::thread(&thread_pool_steal::worker_thread,this,i));
            }