//互斥量:unique_lock类封装,构造函数内加锁,析构函数自动解锁
mutex mut;
unique_lock lock(mut);//范围语句结束才自动释放锁
//条件变量:一般配合互斥量使用
condition_variable cond;
int cond0=1,cond1=0,cond2=0;//类似信号量PV思路
P操作:
for(...){
unique_lock lock(mut);
cond.wait(lock,[this](){return cond0>0;});
if(...){
--cond0;
...dosomething...
cond.notify_all();//范围语句结束才通知所有睡眠线程
}
}
V操作:
for(...){
unique_lock lock(mut);
cond.wait(lock,[this](){return cond0>1;});
if(...){
++cond0;
...dosomething...
cond.notify_all();
}
}
//原子操作:一般只互斥访问一个全局变量的时候使用原子操作,如果获得互斥锁后的操作过多不建议使用
atomic ato;
设计线程安全队列:生产者消费者模型
//线程安全队列
template
class threadSafeQueue{
mutex mut;
condition_variable cond;
queue q;
int capacity;
public:
threadSafeQueue()=default;
~threadSafeQueue()=default;
threadSafeQueue(int c):capacity(c){};
//threadSafeQueue(threadSafeQueue &q)=delete;
//threadSafeQueue(const threadSafeQueue &q)=delete;
threadSafeQueue &operator=(const threadSafeQueue &q)=delete;
void blockpush(const T &in){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->capacity>this->q.size();});
q.push(in);
cond.notify_all();
}
void blockpop(){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->q.size()>0;});
q.pop();
cond.notify_all();
}
T& blockfront(){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->q.size()>0;});
cond.notify_all();
return q.front();
}
bool empty(){
unique_lock lock(mut);
return q.empty();
}
bool full(){
unique_lock lock(mut);
return q.size()==capacity;
}
bool nonblockpush(const T &in){
unique_lock lock(mut);
if(capacity==q.size())return false;
q.push(in);
cond.notify_all();
return true;
}
bool nonblockpop(){
unique_lock lock(mut);
if(q.empty())return false;
q.pop();
cond.notify_all();
return true;
}
bool nonblockfront(T &ret){
unique_lock lock(mut);
if(q.empty())return false;
cond.notify_all();
ret=q.front();
return true;
}
};
我们提供了一个类:
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() 方法之后被执行。
示例 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"。
注意:
尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。
你看到的输入格式主要是为了确保测试的全面性。
//自动变量:效率低
class Foo {
atomic ato;
public:
Foo() {
ato=0;
}
void first(function printFirst) {
printFirst();
++ato;
}
void second(function printSecond) {
while(ato!=1);
printSecond();
++ato;
}
void third(function printThird) {
while(ato!=2);
printThird();
}
};
/*互斥锁+条件变量:效率高
class Foo {
mutex mut;
condition_variable cond;
int cond1,cond2;
public:
Foo() {
cond1=0,cond2=0;
}
void first(function printFirst) {
unique_lock lock(mut);
printFirst();
++cond1;
cond.notify_all();
}
void second(function printSecond) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond1==1;});
printSecond();
++cond2;
cond.notify_all();
}
void third(function printThird) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond2==1;});
printThird();
}
};*/
我们提供一个类:
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
请设计修改程序,以确保 "foobar" 被输出 n 次。
示例 1:
输入: n = 1
输出: "foobar"
解释: 这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。
示例 2:
输入: n = 2
输出: "foobarfoobar"
解释: "foobar" 将被输出两次。
class FooBar {
private:
int n;
mutex mut;
condition_variable cond;
int cond1,cond2;
public:
FooBar(int n) {
this->n = n;
cond1=0;
cond2=1;
}
void foo(function printFoo) {
for (int i = 0; i < n; i++) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond1+1==this->cond2;});
// printFoo() outputs "foo". Do not change or remove this line.
printFoo();
++cond1;
cond.notify_one();
}
}
void bar(function printBar) {
for (int i = 0; i < n; i++) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond1==this->cond2;});
// printBar() outputs "bar". Do not change or remove this line.
printBar();
++cond2;
cond.notify_one();
}
}
};
假设有这么一个类:
class ZeroEvenOdd {
public ZeroEvenOdd(int n) { ... } // 构造函数
public void zero(printNumber) { ... } // 仅打印出 0
public void even(printNumber) { ... } // 仅打印出 偶数
public void odd(printNumber) { ... } // 仅打印出 奇数
}
相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:
线程 A 将调用 zero(),它只输出 0 。
线程 B 将调用 even(),它只输出偶数。
线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506... ,其中序列的长度必须为 2n。
示例 1:
输入:n = 2
输出:"0102"
说明:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 "0102"。
示例 2:
输入:n = 5
输出:"0102030405"
class ZeroEvenOdd {
private:
int n;
mutex mut;
condition_variable cond;
//信号量P V操作,资源个数
bool cond0,cond1,cond2;
public:
ZeroEvenOdd(int n) {
this->n = n;
cond0=true,cond1=false,cond2=false;
}
// printNumber(x) outputs "x", where x is an integer.
void zero(function printNumber) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond0;});
cond0=false;
printNumber(0);
if((i&1)==1)cond1=true;
else cond2=true;
cond.notify_all();
}
}
void even(function printNumber) {
for(int i=2;i<=n;i+=2){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond2;});
cond2=false;
printNumber(i);
cond0=true;
cond.notify_all();
}
}
void odd(function printNumber) {
for(int i=1;i<=n;i+=2){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond1;});
cond1=false;
printNumber(i);
cond0=true;
cond.notify_all();
}
}
};
现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。
存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。
氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。
这些线程应该三三成组突破屏障并能立即组合产生一个水分子。
你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。
换句话说:
如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。
示例 1:
输入: "HOH"
输出: "HHO"
解释: "HOH" 和 "OHH" 依然都是有效解。
示例 2:
输入: "OOHHHH"
输出: "HHOHHO"
解释: "HOHHHO", "OHHHHO", "HHOHOH", "HOHHOH", "OHHHOH", "HHOOHH", "HOHOHH" 和 "OHHOHH" 依然都是有效解。
提示:
输入字符串的总长将会是 3n, 1 ≤ n ≤ 50;
输入字符串中的 “H” 总数将会是 2n 。
输入字符串中的 “O” 总数将会是 n 。
class H2O {
mutex mut;
condition_variable cond;
int cntH,cntO;
public:
H2O() {
cntH=0,cntO=1;//2 2-1/2-0/0-1都可以
}
void hydrogen(function releaseHydrogen) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cntH;});
--cntH;
releaseHydrogen();
if(cntH==0){
++cntO;
cond.notify_all();//
}
}
void oxygen(function releaseOxygen) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cntO;});
--cntO;
releaseOxygen();
cntH+=2;
cond.notify_all();//1 容易漏掉
}
};
编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:
如果这个数字可以被 3 整除,输出 "fizz"。
如果这个数字可以被 5 整除,输出 "buzz"。
如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz"。
例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。
假设有这么一个类:
class FizzBuzz {
public FizzBuzz(int n) { ... } // constructor
public void fizz(printFizz) { ... } // only output "fizz"
public void buzz(printBuzz) { ... } // only output "buzz"
public void fizzbuzz(printFizzBuzz) { ... } // only output "fizzbuzz"
public void number(printNumber) { ... } // only output the numbers
}
请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:
线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。
线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。
线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。
线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。
class FizzBuzz {
private:
int n;
mutex mut;
condition_variable cond;
int condx,cond3,cond5,cond15;
public:
FizzBuzz(int n) {
this->n = n;
condx=1,cond3=0,cond5=0,cond15=0;
}
// printFizz() outputs "fizz".
void fizz(function printFizz) {
for(int i=3;i<=n;i+=3){//1 +=3 否则超时
unique_lock lock(mut);
if(i%15==0)continue;//2 否则超时
cond.wait(lock,[this](){return this->cond3>0;});
--cond3,++condx;
printFizz();
cond.notify_all();
}
}
// printBuzz() outputs "buzz".
void buzz(function printBuzz) {
for(int i=5;i<=n;i+=5){//1 +=5
unique_lock lock(mut);
if(i%15==0)continue;//2
cond.wait(lock,[this](){return this->cond5>0;});
--cond5,++condx;
printBuzz();
cond.notify_all();
}
}
// printFizzBuzz() outputs "fizzbuzz".
void fizzbuzz(function printFizzBuzz) {
for(int i=15;i<=n;i+=15){//1 +=15
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond15>0;});
--cond15,++condx;
printFizzBuzz();
cond.notify_all();
}
}
// printNumber(x) outputs "x", where x is an integer.
void number(function printNumber) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->condx>0;});
if(i%15==0){
++cond15;
--condx;
cond.notify_all();
}
else if(i%3==0){
++cond3;
--condx;
cond.notify_all();
}
else if(i%5==0){
++cond5;
--condx;
cond.notify_all();
}
else printNumber(i);
}
}
};
/*超时
class FizzBuzz {
private:
int n;
mutex mut;
condition_variable cond;
int condx,cond3,cond5,cond15;
public:
FizzBuzz(int n) {
this->n = n;
condx=1,cond3=0,cond5=0,cond15=0;
}
// printFizz() outputs "fizz".
void fizz(function printFizz) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond3;});
if(i%3==0){
--cond3,++condx;
printFizz();
cond.notify_all();
}
}
}
// printBuzz() outputs "buzz".
void buzz(function printBuzz) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond5;});
if(i%5==0){
--cond5,++condx;
printBuzz();
cond.notify_all();
}
}
}
// printFizzBuzz() outputs "fizzbuzz".
void fizzbuzz(function printFizzBuzz) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->cond15;});
if(i%15==0){
--cond15,++condx;
printFizzBuzz();
cond.notify_all();
}
}
}
// printNumber(x) outputs "x", where x is an integer.
void number(function printNumber) {
for(int i=1;i<=n;++i){
unique_lock lock(mut);
cond.wait(lock,[this](){return this->condx;});
if(i%15==0)++cond15,--condx;
else if(i%3==0)++cond3,--condx;
else if(i%5==0)++cond5,--condx;
else printNumber(i);
cond.notify_all();
}
}
};*/
5 个沉默寡言的哲学家围坐在圆桌前,每人面前一盘意面。叉子放在哲学家之间的桌面上。(5 个哲学家,5 根叉子)
所有的哲学家都只会在思考和进餐两种行为间交替。哲学家只有同时拿到左边和右边的叉子才能吃到面,而同一根叉子在同一时间只能被一个哲学家使用。每个哲学家吃完面后都需要把叉子放回桌面以供其他哲学家吃面。只要条件允许,哲学家可以拿起左边或者右边的叉子,但在没有同时拿到左右叉子时不能进食。
假设面的数量没有限制,哲学家也能随便吃,不需要考虑吃不吃得下。
设计一个进餐规则(并行算法)使得每个哲学家都不会挨饿;也就是说,在没有人知道别人什么时候想吃东西或思考的情况下,每个哲学家都可以在吃饭和思考之间一直交替下去。
哲学家从 0 到 4 按 顺时针 编号。请实现函数 void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork):
philosopher 哲学家的编号。
pickLeftFork 和 pickRightFork 表示拿起左边或右边的叉子。
eat 表示吃面。
putLeftFork 和 putRightFork 表示放下左边或右边的叉子。
由于哲学家不是在吃面就是在想着啥时候吃面,所以思考这个方法没有对应的回调。
给你 5 个线程,每个都代表一个哲学家,请你使用类的同一个对象来模拟这个过程。在最后一次调用结束之前,可能会为同一个哲学家多次调用该函数。
示例:
输入:n = 1
输出:[[4,2,1],[4,1,1],[0,1,1],[2,2,1],[2,1,1],[2,0,3],[2,1,2],[2,2,2],[4,0,3],[4,1,2],[0,2,1],[4,2,2],[3,2,1],[3,1,1],[0,0,3],[0,1,2],[0,2,2],[1,2,1],[1,1,1],[3,0,3],[3,1,2],[3,2,2],[1,0,3],[1,1,2],[1,2,2]]
解释:
n 表示每个哲学家需要进餐的次数。
输出数组描述了叉子的控制和进餐的调用,它的格式如下:
output[i] = [a, b, c] (3个整数)
- a 哲学家编号。
- b 指定叉子:{1 : 左边, 2 : 右边}.
- c 指定行为:{1 : 拿起, 2 : 放下, 3 : 吃面}。
如 [4,2,1] 表示 4 号哲学家拿起了右边的叉子。
提示:
1 <= n <= 60
class DiningPhilosophers {
atomicseat;
public:
DiningPhilosophers() {
seat=4;
}
void wantsToEat(int philosopher,
function pickLeftFork,
function pickRightFork,
function eat,
function putLeftFork,
function putRightFork) {
if(seat>0){
--seat;
pickLeftFork();
pickRightFork();
eat();
putLeftFork();
putRightFork();
++seat;
}
}
};
/*
class DiningPhilosophers {
mutex mut;
condition_variable cond;
int seat;
public:
DiningPhilosophers() {
seat=4;
}
void wantsToEat(int philosopher,
function pickLeftFork,
function pickRightFork,
function eat,
function putLeftFork,
function putRightFork) {
unique_lock lock(mut);
cond.wait(lock,[this](){return this->seat;});
--seat;
pickLeftFork();
pickRightFork();
eat();
putLeftFork();
putRightFork();
++seat;
cond.notify_one();
}
};*/