【问题标题】:Why is this c++ multithreading mutex code exhibiting occasional failures?为什么这个 c++ 多线程互斥代码偶尔会出现故障?
【发布时间】:2018-03-04 06:13:12
【问题描述】:

我在 linux Debian 系统上使用下面的 foo.cpp 代码:

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <thread>

std::mutex mtx;
std::condition_variable cvar;
long next = 0;

void doit(long index){
  std::unique_lock<std::mutex> lock(mtx);
  cvar.wait(lock, [=]{return index == next;});

  std::cout<< index << std::endl;
  ++next;

  mtx.unlock();
  cvar.notify_all();

  return;
}

int main() 
{
  long n=50;

  for (long i=0; i < n; ++i)
    std::thread (doit,i).detach();

  while(next != n)
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

  return(0);
}

我编译它:

g++ -std=c++14 -pthread -o foo foo.cpp

它旨在触发 50 个分离的线程,这些线程由函数 doit 中的互斥锁和条件变量控制,因此它们按顺序执行互斥锁块。

它大部分时间都在工作,将数字 00 到 49 写入屏幕,然后终止。

但是,它有两种偶尔的故障模式:

失败模式1:在上升到某个任意数字

foo: ../nptl/pthread_mutex_lock.c:80: __pthread_mutex_lock: 断言 `mutex->__data.__owner == 0' 失败。

失败模式 2:在达到任意数字

我会很感激有关此行为的原因以及如何解决它的任何建议。

================================================ ============================

编辑:好的,这是一个有效的修订版本。我修复了这两个错误,并将锁名称从“lock”更改为“lk”以减少混淆。感谢您的帮助。

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>

std::mutex mtx;
std::condition_variable cvar;
long next = 0;

void doit(long index){

  std::unique_lock<std::mutex> lk(mtx);
  cvar.wait(lk, [=]{return index == next;});

  std::cout<< index << std::endl;
  ++next;

  lk.unlock();
  cvar.notify_all();

  return;
}

int main()
{
  long n=50;

  for (long i=0; i < n; ++i)
    std::thread (doit,i).detach();

  {
    std::unique_lock<std::mutex> lk(mtx);
    cvar.wait(lk, [=]{return n == next;});
  }

  return(0);
}

【问题讨论】:

    标签: c++ multithreading pthreads mutex condition-variable


    【解决方案1】:

    while(next != n) 尝试访问变量next,该变量可由工作线程修改,而无需任何同步创建竞争条件。它应该被相同的互斥锁覆盖:

    {
       std::unique_lock<std::mutex> lock(mtx);
       cvar.wait(lock, [=]{return n == next;});
    }
    

    分离线程不是一个好主意。在从main 返回之前,您应该将它们存储在某个地方然后join

    更新:您试图在 mutex 本身上调用 unlock,而不是在锁定对象上调用它。通过构造锁定对象,您将解锁互斥锁的责任委托给lock 对象。应该是

    lock.unlock();
    cvar.notify_all();
    

    【讨论】:

    • 感谢这个带有 while(next != n) 条件的建议。然而,使用上述锁定/等待代码仍然会导致相同的故障模式。我很好奇为什么在这种情况下分离不是一个好主意?
    • @Leo Detaching 通常不是一个好主意,因为您会失去对线程的控制。
    • 嗯,好的。是的,最后一次更新修复了它。这显然是错误,它与en.cppreference.com/w/cpp/thread/condition_variable 一致......谢谢!
    【解决方案2】:

    我不建议分离线程,因为之后您将无法加入它们。 如果你真的想做,那就使用条件变量来同步数据,等待下一次。

    void doit(long index){
      std::unique_lock<std::mutex> lock(mtx);
      cvar.wait(lock, [=]{return index == next;});
    
      std::cout<< index << std::endl;
      ++next;
    
      cvar.notify_all();
    
      return;
    }
    
    int main() 
    {
      long n=50;
    
      for (long i=0; i < n; ++i)
        std::thread (doit,i).detach();
    
      //here you wait for the last thread to finish
      {
         std::unique_lock<std::mutex> lock(mtx);
         cvar.wait(lock, [=]{return n == next;});
      }
    
      return(0);
    }
    

    如果您可以让您的线程可连接,您可以编写更简单的代码。

    std::mutex mtx;
    std::condition_variable cvar;
    long next = 0;
    
    void doit(long index){
      std::unique_lock<std::mutex> lock(mtx);
    
      //this guarantees the order in which are being executed
      cvar.wait(lock, [=]{return index == next;});
    
      std::cout<< index << std::endl;
      ++next;
    
      cvar.notify_all();//wakes all the thread, only the one with index=next will be executed
    
      return;
    }
    
    int main() 
    {
        long n=50;
        std::vector<std::thread> workers;
    
        for (long i=0; i < n; ++i){
          workers.emplace_back(std::thread (doit,i));
        }
    
        //this guarantees your threads are all finished at the end of this block
        for (auto& t : workers) {
            t.join();
        }
    
      return(0);
    }
    

    【讨论】:

    • 无法加入线程并不总是一件坏事。有时你不想想要加入一个话题。
    • 这取决于你想做什么。这里的问题看起来像一个生产者/消费者。在这里加入线程可能是一件好事
    • 此示例代码可以按照建议使用可连接线程编写。但是,给我带来这个问题的相关代码是在客户端-服务器环境中运行的服务器端代码。服务器在无限循环中运行,当它得到客户端请求的工作时,它会启动必要数量的线程并返回侦听其他请求。因此,在 doit() 的完整版本中,被分离的线程需要按照它们被分离的顺序执行一些特定的任务,然后再也不需要听到它们的消息。在此设置中分离它们是有意义的。
    • 好的,明白了。我为其他可以查看答案的人留下了不可拆卸的线程。如果您对答案感到满意,请投票以将其标记为相关
    【解决方案3】:

    为什么不保持简单?

    int main() {
        long n = 50;
        std::vector<std::thread> threads;
    
        for (long i = 0; i < n; ++i)
            threads.emplace_back([=]() { std::cout << i << std::endl; });
    
        for (const auto& t : threads) {
            t.join();
        }
    
        return 0;
    }
    

    【讨论】:

    • 这个方案不能保证线程的执行顺序。
    【解决方案4】:

    试试这个 sn-p:你不应该使用 mtx.unlock() 并让 condition_variable 来完成这项工作。也可以使用 std::ref 将函数参数传递给线程。

    std::mutex mtx;
    std::condition_variable cvar;
    bool ready = true;
    
    void doit(long index) {
        std::unique_lock<std::mutex> lock(mtx);
        cvar.wait(lock, [=] {return ready == true; });
        ready = false;
        std::cout << index << std::endl;
    
        ready = true;
        cvar.notify_all();
    
        return;
    }
    
    int main()
    {
        long n = 50;
    
        for (long i = 0; i < n; ++i)
            std::thread(doit, std::ref(i)).detach();
    
        std::this_thread::sleep_for(std::chrono::seconds(3));
    
        return(0);
    }
    

    【讨论】:

    • 谢谢!删除 mtx.unlock() 就可以了。上述其他更改对我不起作用,因为我需要线程按顺序 0 1 2 ... 49 执行互斥块。但是,通过删除 mtx.unlock() 解决了问题。
    • 你为什么用std::ref
    【解决方案5】:

    std:: unique_lock 是一个 RAII 对象。在一个范围内声明它,并将你的关心抛诸脑后。这就是问题所在:在 doit 调用 mtx.unlock() 之后,偶尔下一条语句 cvar.notify_all() 会立即用(新的)next == 索引唤醒线程。该线程将获取互斥锁。当 doit 返回时,锁析构函数尝试释放互斥体,但它被另一个线程持有。灾难接踵而至。 doit() 方法如下:

    void doit(long index) {
        {
            std::unique_lock<std::mutex> lock(mtx);
            cvar.wait(lock, [=] {return index == next; });
            ++next;
            std::cout << index << std::endl;
        }
    
        cvar.notify_all(); 
    
        return;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-28
      • 2015-05-27
      • 2013-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-28
      相关资源
      最近更新 更多