多线程按序调度

严正声明:本文系作者davidhopper原创,未经许可,不得转载。

下述问题来源于按序打印

一、问题描述

提供一个类(原题描述以Java语言提供,本文使用C++实现

public class Foo {
  public void one() { print("one"); }
  public void two() { print("two"); }
  public void three() { print("three"); }
}

三个不同的线程将会共用一个Foo实例。
线程A将会调用one()方法
线程B将会调用two()方法
线程C将会调用three()方法

请设计修改程序,以确保two()方法在one() 方法之后被执行,three()方法在 two() 方法之后被执行,亦即,无论操作系统内部按照什么样的顺序调用线程ABC,执行的顺序永远是one()->two()->three(),输出的结果永远是onetwothree

示例 1:

输入: [1,2,3]
输出: "onetwothree"
解释: 
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 two() 方法,线程 C 将会调用 three() 方法。
正确的输出是 "onetwothree"。

示例 2:

输入: [1,3,2]
输出: "onetwothree"
解释: 
输入 [1,3,2] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 three() 方法,线程 C 将会调用 two() 方法。
正确的输出是 "onetwothree"。

注意:

尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。你看到的输入格式主要是为了确保测试的全面性。

二、问题分析

一般的解决方案是利用std::mutex锁定临界区以指定线程的执行顺序,该思路没有问题,但一不小心就容易造成死锁。本文使用C++ 11中自带的std::promise来实现,逻辑更为简洁。首先看std::promise的介绍
std::promise是一个模板类template<typename R> class promise,其泛型参数Rstd::promise对象保存的值的类型,R可以是void类型。std::promise保存的值可被与之关联的std::future对象读取,读取操作可以发生在其它线程。std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future亦然。std::promisestd::future合作共同实现了多线程间通信

2.1 线程随机调度的程序

下面给出线程随机调度的程序:

#include <functional>
#include <iostream>
#include <thread>

void printFirst() { std::cout << "first"; }
void printSecond() { std::cout << "second"; }
void printThird() { std::cout << "third"; }

class Foo {
 public:
  void first(const std::function<void()>& printFirst) {
    printFirst();
  }

  void second(const std::function<void()>& printSecond) {
    printSecond();
  }

  void third(const std::function<void()>& printThird) {
    printThird();
  }
};

void invokeFooFirst(Foo& foo) { foo.first(printFirst); }
void invokeFooSecond(Foo& foo) { foo.second(printSecond); }
void invokeFooThird(Foo& foo) { foo.third(printThird); }

int main() {
  Foo foo;
  std::thread t2(invokeFooSecond, std::ref(foo));
  std::thread t1(invokeFooFirst, std::ref(foo));
  std::thread t3(invokeFooThird, std::ref(foo));

  t3.join();
  t1.join();
  t2.join();

  std::cout << std::endl;

  return 0;
}

在Linux系统中的编译指令为(注意使用C++11以上版本):

g++ -g thread_order.cpp -o thread_order -lpthread

在我的机器上执行结果为:

secondfirstthird

2.2 线程按序调度的程序

下面给出线程按序调度的程序:
不管三个线程如何被操作系统调度,永远是先执行first()并设置second()触发的信号;second()一直挂起直到等到first()设置的触发信号,third()一直挂起直到等到second()设置的触发信号。通过上述简单的信号传递机制,确保了first()second()third()的按序执行。

#include <functional>
#include <future>
#include <iostream>
#include <thread>

void printFirst() { std::cout << "first"; }
void printSecond() { std::cout << "second"; }
void printThird() { std::cout << "third"; }

class Foo {
 public:
  void first(const std::function<void()>& printFirst) {
    printFirst();
    p1_.set_value();
  }

  void second(const std::function<void()>& printSecond) {
    p1_.get_future().wait();
    printSecond();
    p2_.set_value();
  }

  void third(const std::function<void()>& printThird) {
    p2_.get_future().wait();
    printThird();
  }

 private:
  std::promise<void> p1_;
  std::promise<void> p2_;
};

void invokeFooFirst(Foo& foo) { foo.first(printFirst); }
void invokeFooSecond(Foo& foo) { foo.second(printSecond); }
void invokeFooThird(Foo& foo) { foo.third(printThird); }

int main() {
  Foo foo;
  std::thread t2(invokeFooSecond, std::ref(foo));
  std::thread t1(invokeFooFirst, std::ref(foo));
  std::thread t3(invokeFooThird, std::ref(foo));

  t3.join();
  t1.join();
  t2.join();

  std::cout << std::endl;

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值