如何从两个线程一个接一个地运行两个循环,就像触发器一样?



我有一个问题,类似地回答了这里,但它不是我所需要的。我有两个线程,每个线程都有一个循环。现在我想让两个线程像人字拖一样工作。就像这个ABABAB或BABABA..。谁先开始不重要,重要的是要一个一个地干。

我有一个非常简单的代码,但它不能很好地工作,因为线程a迭代超级事实并再次获得锁。请帮助我,因为我正在学习c++多线程。

1-在上面的链接中,它说也许有两个线程不是最好的方法。假设这是一个游戏,我必须为玩家a和玩家B分别运行一次迭代。我同意它并没有给我更好的效率,因为在每个时刻只有一个在工作,我想知道是否有一种方法。

int pointA , pointB;
void testA()
{
int i = 0;
while (i < 10)
{
unique_lock<std::mutex> lck(mtx);
cout << pointB << endl;
pointA++;
i++;
}

}

void main()
{
int i = 0;
pointA =100, pointB=0;
thread t(testA);
while (i < 10)
{
unique_lock<std::mutex> lck(mtx);
cout << pointA << endl;
pointB++;
i++;
}

t.join();
}

在不使用标准的情况下,如果允许在多个线程中使用相同的代码,您可以通过变量来分支流:

// in both threads
unique_lock<std::mutex> lck(mtx);
if(var && myId == "A")
{
// stuff that A does
var = false;
}
else if(!var && myId == "B")
{
// stuff that B does
var = true;
}

但是这将是因为在其他情况下,id值与变量条件不匹配,检查每个情况会使变得更慢.

c++在这方面有一些帮助:

std::condition_variable 

通过使用条件变量,您可以为每个线程自动触发不同的条件以停止等待:

std::condition_variable cv;
...
std::unique_lock lk(mtx);
cv.wait(lk, []{return your_logic();});

因为它只是等待,所以不会像第一个例子那样浪费CPU周期。不必要的唤醒/处理变得更低,内存带宽也不会浪费。


更隐式的组合两个线程输出的方法是使用两个线程安全队列,一个从A到B,一个从B到输出:

// assuming the implementation blocks .front() until it is filled
ThreadSafeQueue q1;
ThreadSafeQueue q2;
// in thread A
for(int i=0;i<10;i++)
q1.push("A"); 
// in thread B
for(int i=0;i<10;i++)
{
q2.push(q1.front()+"B");
q1.pop();
}
// in main thread
auto result = q2.front();  // "AB"
q2.pop();

在这种模式下,线程b对于线程a的每个结果只工作一次。但这不会同步线程。线程a可以用10来填充队列。值在线程b处理第5个";AB";并且在主线程得到第三个"AB"之前。

为了及时执行类似触发器的工作,您可以将队列的大小限制为1或2。然后它会阻塞线程a,直到线程b消耗它,第二个队列会阻塞线程b,直到主线程消耗它。


另一种为不同任务同步多线程的方法是使用循环屏障:

[C++20]
std::barrier sync_point(size /*2?*/, func_on_completion);
// in thread A 
..stuff..flip..
sync_point.arrive_and_wait();
..more stuff that needs updated stuff..
// in thread B
..stuff..flop..
sync_point.arrive_and_wait();
..more stuff that needs updated stuff..

barrier确保两个线程在继续之前互相等待。如果这是一个循环,那么它们将一次处理一个步骤(1步骤意味着a和B同时产生),同时在进行下一次迭代之前相互等待。所以它会产生abbaababababaab同时不会产生更多的A或B。如果A总是需要在B之前,那么你需要更多的障碍来确保顺序:

// in thread A and B
if(thread is A) 
output "A"
sync_point.arrive_and_wait();
if(thread is B) 
output "B"
sync_point.arrive_and_wait();   

this打印ABABABAB…

如果你使用的是OpenMP,它也有障碍:

#pragma omp parallel
{
...work...
#pragma omp barrier
...more work...
}

如果您不希望第二部分与下一次迭代的第一部分同时发生,您需要两个障碍:

for(...)
#pragma omp parallel
{
...work...
#pragma omp barrier
...more work...
#pragma omp barrier
}

如果两个线程在每次迭代中工作的顺序仍然很重要,这将需要为每个线程提供专用段

for(...)
#pragma omp parallel
{
if(thread is A?)
do this
#pragma omp barrier
if(thread is B?)
do that
#pragma omp barrier
}

这将始终写入ABABAB,尽管效率降低,因为OpenMP块启动/停止开销很高,并且在循环中可测量。最好在每个线程中都有一个循环:

#pragma omp parallel num_threads(2)
{
// this loop runs same for both threads, not shared/parallelized
for(int i=0;i<10;i++)
{
int id=omp_get_thread_num();
if(id==0)
std::cout<<"A"<<std::endl;
#pragma omp barrier
if(id==1)
std::cout<<"B"<<std::endl;
#pragma omp barrier
}
}

输出ABABABABAB…并且没有openmp启动/停止开销(但仍然存在屏障开销)。

基于这个答案和上面的答案,我设法编写了代码。我们需要一面旗子来在两个循环之间切换。还有另一种现成的方法,这里解释得很好,它是在c#中,但概念是一样的:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mutex1;
condition_variable cv3;
char turn;
void ThreadA()
{
for (int i = 0; i < 1000; i++)
{
unique_lock<mutex> lock(mutex1);
cv3.wait(lock, [] {return (turn == 'A'); });
cout << "Thread A" << endl;
turn = 'B';
cv3.notify_all();
}
}
void ThreadB()
{
for (int i = 0; i < 1000; i++)
{
unique_lock<mutex> lock(mutex1);
cv3.wait(lock, [] {return (turn == 'B'); });
cout << "Thread B" << endl;
turn = 'A';
cv3.notify_all();
}
}
void ExecuteThreads()
{
turn = 'A';
std::thread t1(ThreadA);
std::thread t2(ThreadB);
t1.join();
t2.join();
std::cout << "Finished" << std::endl;
}
int main()
{
ExecuteThreads();
return 0;
}

最新更新