严正声明:本文系作者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() 方法之后被执行,亦即,无论操作系统内部按照什么样的顺序调用线程A、B、C,执行的顺序永远是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,其泛型参数R为std::promise对象保存的值的类型,R可以是void类型。std::promise保存的值可被与之关联的std::future对象读取,读取操作可以发生在其它线程。std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future亦然。std::promise和std::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;
}
本文介绍了一种使用C++11中的std::promise和std::future来确保多个线程按特定顺序执行的方法,避免了传统互斥锁可能带来的死锁问题。
10万+

被折叠的 条评论
为什么被折叠?



