当前位置: 首页 > 知识库问答 >
问题:

如何同步运行在不同线程上的函数的实例(在C++11中)?

西门建安
2023-03-14

假设有多个线程由运行相同函数实例的循环组成,但是每个迭代的开始都需要同步(所以首先完成的线程必须等待最后一个线程开始新的迭代)。在C++11中如何做到这一点?

...

这在大多数情况下都起作用,但有时会有一两个线程无法唤醒。

这些是全局变量

mutex syncMutex;
condition_variable syncCV;
int sync;

这是在线程中循环运行的函数的末尾:

unique_lock<mutex> lk(syncMutex);
cout << "Thread num: " << mFieldNum << " got sync value: " << sync;
sync --;
syncCV.notify_all();
cout << " and goes to sleep..." << endl;
syncCV.wait(lk, []{return sync == numFields;});
cout << "Thread num: " << mFieldNum << " woke up" << endl;
}
unique_lock<mutex> lk(syncMutex);
syncCV.wait(lk, []{return sync == 0;});
sync = 3;
lk.unlock();
cout << "Notifying all threads!" << endl;
syncCV.notify_all();
Thread num: 1 got sync value: 3 and goes to sleep...
Thread num: 2 got sync value: 2 and goes to sleep...
Thread num: 3 got sync value: 1 and goes to sleep...
Notifying all threads!
Thread num: 1 woke up
Thread num: 2 woke up
Thread num: 3 woke up
Thread num: 2 got sync value: 3 and goes to sleep...
Thread num: 1 got sync value: 2 and goes to sleep...
Thread num: 3 got sync value: 1 and goes to sleep...
Notifying all threads!
Thread num: 2 woke up
Thread num: 1 woke up
Thread num: 2 got sync value: 3 and goes to sleep...
Thread num: 1 got sync value: 2 and goes to sleep...

共有1个答案

百里伟
2023-03-14

线程同步存在许多问题。托尼在他的评论中提到了一个。在主循环代码中还有一个潜在的竞争条件,即在调用synccv.notify_all()之前调用lk.unlock()。(这可能允许线程错过notify_all信号。)

我将以两种方式调整您的代码。首先,为了解决使用“sync==numfields”作为条件的问题(正如Tony所指出的,在另一个线程执行了sync之后可能会失败),使用每个线程在每个主线程循环中只运行一次作为条件是有意义的。在我的示例代码中,这是通过引入“done[numFields]”变量来实现的。其次,引入两个条件变量是有意义的--一个用于通知工作线程新的主循环迭代已经开始,另一个用于通知主线程工作线程已经完成。(请注意,这两个条件变量使用相同的互斥体。)

下面是一个完整的程序,它以示例代码为模型,包含了以下两种方法:

#include <iostream>
using std::cout;
using std::endl;

#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>

std::mutex syncMutex;
std::condition_variable readyCV;
std::condition_variable doneCV;
int sync;
bool exitFlag;

const int numFields = 5;
bool done[numFields];

const int nloops = 10;

void thread_func(int i) {
  int mFieldNum = i;
  while (true) {
    std::unique_lock<std::mutex> lk(syncMutex);
    readyCV.wait(lk, [mFieldNum]{return  exitFlag || !done[mFieldNum-1];});
    if (exitFlag)  break;
    cout << "Thread num: " << mFieldNum << " woke up, got sync value: " << sync;
    if (--sync == 0)  doneCV.notify_all();
    done[mFieldNum-1] = true;
    readyCV.notify_all();
    cout << " and goes to sleep..." << endl;
  }
}

int main (int argc, char* argv[]) {
  exitFlag = false;
  sync = 0;
  std::vector<std::thread> threads;
  for (int i = 0; i < numFields; i++) {
    done[i] = true;
    threads.emplace_back (thread_func, i+1);
  }
  for (int i = 0; i <= nloops; i++) {
    std::unique_lock<std::mutex> lk(syncMutex);
    doneCV.wait(lk, []{return sync == 0;});
    cout << "main loop (lk held), i = " << i << endl;
    sync = numFields;
    if (i == nloops)  exitFlag = true;
    else              for (auto &b : done)  b = false;
    cout << "Notifying all threads!" << endl;
    readyCV.notify_all();
  }

  for (auto& t : threads)  t.join();
}
#include <iostream>
using std::cout;
using std::endl;

#include <atomic>
#include <mutex>
#include <thread>
#include <vector>

std::mutex coutMutex;

std::atomic<int> sync;

const int numFields = 5;
bool done[numFields];

const int nloops = 10;

void thread_func(int i) {
  int mFieldNum = i;
  int mySync = sync--;
  {
    std::lock_guard<std::mutex> lk(coutMutex);
    cout << "Thread num: " << mFieldNum << " woke up, got sync value: " << mySync << endl;
  }
}  

int main (int argc, char* argv[]) {
  for (int i = 0; i < nloops; i++) {
    cout << "main loop, i = " << i << endl;
    std::vector<std::thread> threads;
    sync = numFields;
    for (int i = 0; i < numFields; i++)  threads.emplace_back (thread_func, i+1);
    for (auto& t : threads)  t.join();
  }
}

(coutMutex是一种精确的方法,这样控制台输出就不会乱码,但对于核心同步逻辑来说,它不是必需的。)

如果在您的实际用例中,您不需要thread_func从一个迭代到另一个迭代保持活动状态(例如,保持某种状态),并且如果每次对thread_func的调用所做的工作足以使创建一个新线程来运行它的成本与此相比并不重要,那么为每次主循环迭代创建新线程(而不是重用线程)就会变得直接、合理和简单。

快乐的多线程黑客!

K.弗兰克

 类似资料:
  • 问题内容: 我知道在方法带来同步到该对象之前使用关键字。也就是说,运行对象的同一实例的2个线程将被同步。 但是,由于同步是在对象级别进行的,因此运行对象的不同实例的2个线程将不会同步。如果我们在Java类中有一个由该方法调用的静态变量,则希望它在该类的实例之间同步。这两个实例在2个不同的线程中运行。 我们可以通过以下方式实现同​​步吗? 确实是因为我们定义了一个静态对象,并且正在使用该锁的关键字,

  • 我是JavaScript和React的新手,我正试图远离教程,所以为了自己的学习利益,我开始制作一个简单的应用程序,但遇到了功能异步运行的障碍。 在中,有一个,其回调中包含以下内容: 如何使上述两个函数同步运行使用在中设置的,但下面的显示在完成之前启动。 我已经阅读了一些参考资料,其中提到使用promise,但我不知道如何应用它。。。我还想知道是否可以通过/实现。 完整(ish)代码:(为了简单起

  • 我正在运行RxJava并创建一个主题以使用方法生成数据。我正在使用Spring。 这是我的设置: 在RxJava流上生成新数据的方式是通过Autowire private SubjectObserver SubjectObserver,然后调用SubjectObserver。发布(newDataObjGenerated) 无论我为subscribeOn()指定了什么 Schedulers.io()

  • 我有一个情况,我需要启动两个线程一个接一个。我尝试了以下代码片段,在这里我可以启动Thread12,但不能启动Thread2。我怎样才能开始两个......?如何启动两个线程一个接一个...? 代码片段

  • 本文向大家介绍C# 中如何实现线程同步?相关面试题,主要包含被问及C# 中如何实现线程同步?时的应答技巧和注意事项,需要的朋友参考一下 (1)方法一:阻塞(调用Sleep()或Join()) (2)方法二:加互斥锁lock (3)方法三:信号和句柄(AutoResetEvent/ManualResetEvent,调用Set()和WaitOne())    

  • 我在一个用C语言编写的多线程服务器应用程序上工作,并在嵌入式Linux上执行。一个线程(我称之为通信线程)应该处理所有套接字I/O(发送和接收消息)。依赖于接收到的消息,通信线程将消息发送到另一个线程(例如Thread)。Controller-Thread)处理所需的序列。控制器线程在序列的末尾创建返回消息。此消息被写回通信线程,该线程应该将它们传输到客户端。 这两个线程之间的通信是通过队列实现的