这个多线程程序中真的需要互斥量和/或条件变量吗?

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

给出如下三个函数:

void print1() {
    cout << "one";
    cout.flush();
}
void print2() {
    cout << "two";
    cout.flush();
}
void print3() {
    cout << "three";
    cout.flush();
}

我需要生成三个线程,其中每个线程用于运行上面的每个函数。我们可以假设我无法控制 CPU 如何调度线程,但这三个函数必须以正确的顺序执行。也就是说输出必须是“onetwothird”。

我已经阅读了许多关于 mutexconditional_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
可能不是原子的,这会导致竞争条件。但根据我的研究,变量的比较操作和赋值操作是原子的。那么,我可以说我的解决方案是万无一失的吗?

c++ multithreading race-condition
1个回答
0
投票

在“多线程”标签下响应,我提供了另一种解决方案。

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

任务入口是任务同步点。可以通过条目传递数据,这样条目也可以用作更复杂的事件通知。

© www.soinside.com 2019 - 2024. All rights reserved.