给出如下三个函数:
void print1() {
cout << "one";
cout.flush();
}
void print2() {
cout << "two";
cout.flush();
}
void print3() {
cout << "three";
cout.flush();
}
我需要生成三个线程,其中每个线程用于运行上面的每个函数。我们可以假设我无法控制 CPU 如何调度线程,但这三个函数必须以正确的顺序执行。也就是说输出必须是“onetwothird”。
我已经阅读了许多关于 mutex 和 conditional_variable 的解决方案和材料,我知道这个任务应该用它们来解决。但我认为这个任务仍然可以只用一个普通变量来解决,而不需要任何多线程工具,例如互斥锁、原子变量等。例如,这是我的解决方案:
#include <functional> // function
#include <iostream>
#include <thread>
using namespace std;
class Foo {
int turn;
public:
Foo() { turn = 1; }
void first(function<void()> printFirst) {
while (turn != 1) {
}
printFirst();
turn = 2;
}
void second(function<void()> printSecond) {
while (turn != 2) {
}
printSecond();
turn = 3;
}
void third(function<void()> printThird) {
while (turn != 3) {
}
printThird();
turn = 1;
}
};
void print1() {
cout << "first";
cout.flush();
}
void print2() {
cout << "second";
cout.flush();
}
void print3() {
cout << "third";
cout.flush();
}
int main() {
Foo f;
thread thread3([&]() { f.third(print3); });
thread thread2([&]() { f.second(print2); });
thread thread1([&]() { f.first(print1); });
thread1.join();
thread2.join();
thread3.join();
return 0;
}
本质上,
turn
中的一个公共属性Foo
用于控制正在运行的线程的流程。我特别首先生成 thread3
来表明 turn
变量确实可以控制 thread1
在 thread3
之前执行。我已经运行这个程序好几次了,结果始终显示“onetwo Three”。
所以,我的问题是,鉴于任务的设置,我的解决方案是否存在漏洞?
我只能想到一个可能的漏洞,那就是比较操作
turn != 1
和赋值操作turn = 1
可能不是原子的,这会导致竞争条件。但根据我的研究,变量的比较操作和赋值操作是原子的。那么,我可以说我的解决方案是万无一失的吗?
在“多线程”标签下响应,我提供了另一种解决方案。
Ada语言提供了任务间同步通信的交会机制,轻松解决了这个任务排序问题。任务通常使用操作系统线程来实现。
以下示例演示了不使用互斥体的这种排序。
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
task T1;
task T2 is
entry Start;
end T2;
task t3 is
entry Start;
end t3;
task body T1 is
begin
Put("one");
T2.start;
end T1;
task body t2 is
begin
accept Start;
Put("two");
T3.Start;
end T2;
task body t3 is
begin
accept Start;
Put("three");
end T3;
begin
null;
end Main;
定义了三项任务。任务 T1 没有其他任务可以调用的条目,而任务 t2 和 t2 各有一个名为 Start 的条目。
任务条目实现集合通信方法。调用另一个任务中的条目的任务将挂起,直到被调用的任务接受条目调用。同样,接受条目调用的任务会在接受语句处挂起,直到其他任务调用该条目。
任务 t1 的任务主体输出“one”并调用 t2 的 Start 条目。
T2 的任务主体接受 Start,输出“two”并调用 t3 的 Start 条目。
T3 的任务主体接受 Start,然后输出“三”。
该程序的输出是:
onetwothree
任务入口是任务同步点。可以通过条目传递数据,这样条目也可以用作更复杂的事件通知。