为什么这个c ++多线程互斥代码偶尔会出现故障?

问题描述 投票:1回答:5

我在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中的互斥锁和condition_variable控制,因此它们按顺序执行互斥锁。

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

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

故障模式1:在达到某个任意数字<50之后,它会因错误而中止:

foo:../ nptl / pthread_mutex_lock.c:80:__ pthread_mutex_lock:断言`mutex - > __ data .__ owner == 0'失败。

失败模式2:在达到某个任意数字<50之后,它会挂起,并且必须用ctrl-C杀死才能返回到终端提示符。

我将不胜感激任何有关此行为的原因的建议,以及我如何解决它。

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

编辑:好的,所以这是一个工作修订版。我修复了两个错误,并将锁定名称从“锁定”更改为“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
5个回答
3
投票

while(next != n)尝试访问变量next,可以通过工作线程修改,而不会产生任何同步,从而产生竞争条件。它应该由相同的互斥锁覆盖:

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

分离线程不是一个好主意。你应该把它们存放在某个地方然后joinmain返回之前。

更新:你试图在unlock上调用mutex而不是在锁定对象上调用它。通过构造锁定对象,您将负责解锁互斥锁到lock对象。它应该是

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

0
投票

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

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);
}

-1
投票

为什么不保持简单?

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;
}

-1
投票

试试这个片段:你不应该使用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);
}

-1
投票

std:: unique_lock是RAII对象。在一个范围内宣布它并将您的关注投入到风中。这是问题:在doit调用mtx.unlock()之后,有时候下一个语句cvar.notify_all()会立即用(new)next == index唤醒线程。该线程将获取互斥锁。当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;
}
© www.soinside.com 2019 - 2024. All rights reserved.