- 之前在Linux下操作C的API pthread相关函数 写多线程,或者使用Qt的QThread实现多线程编程,不太具备通用性
- C++11相较于C++98添加了蛮多的多线程编程的特性,本文记录下其中部分知识。
1. 基础知识
C++ 多线程编程入门基础: 学习链接
C++并发编程 - 同步并发操作:添加链接描述
2. 按序打印
代码解法类似 1.基础知识 里面的最后一个案例
class Foo {
public:
Foo() {
}
void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
flag1 = true;
cv.notify_all();
}
void second(function<void()> printSecond) {
unique_lock<mutex> loc(m);
while(flag1==false){
cv.wait(loc);
}
// printSecond() outputs "second". Do not change or remove this line.
printSecond();
flag2 = true;
cv.notify_all();
}
void third(function<void()> printThird) {
unique_lock<mutex> loc(m);
while(flag2==false){
cv.wait(loc);
}
// printThird() outputs "third". Do not change or remove this line.
printThird();
}
private:
mutex m;
condition_variable cv;
bool flag1 = false;
bool flag2 = false;
};
3.交替打印FooBar
condiation_variable的wait方法有两种:有条件的等待和无条件的等待
wait有条件等待与无条件等待
class FooBar {
private:
int n;
mutex mtx;
condition_variable cv;
bool foo_done=false;
public:
FooBar(int n) {
this->n = n;
}
void foo(function<void()> printFoo) {
for (int i = 0; i < n; i++) {
unique_lock<mutex> locker(mtx);
while(foo_done==true)
cv.wait(locker);
// cv.wait(locker,[&](){return foo_done==false;})
printFoo();
foo_done=true;
cv.notify_one();
}
}
void bar(function<void()> printBar) {
for (int i = 0; i < n; i++) {
unique_lock<mutex>locker(mtx);
while(foo_done==false)
cv.wait(locker);
//cv.wait(locker,[&](){return foo_done==true;}) 除了while还可以采用条件wait
printBar();
foo_done=false;
cv.notify_one();
}
}
4. 汇总
/*************************
example1 - join :
main是主线程,t1是主线程中的子线程,在这个线程里执行函数fun1()。
使用join()函数,那么主线程就会一直阻塞在t1.join();的位置,直到子线程t1运行结束后,再接着执行后面的语句。
fun1()函数里写了一个休眠1s的语句,所以t1线程在还没有打印fun1的时候,主线程由于没有写join()函数阻塞起来,就会继续往后执行,先打印出main,等t1休眠了1s后,才打印出fun1。
join()函数除了阻塞作用,另一个作用就是回收该线程使用到的资源,上面的程序把join()删掉了,子线程t1的资源不能正确回收,程序运行会报错“abort() has been called”。
**************************/
#if 0
#include<iostream>
#include <thread>
#include <chrono>
using namespace std;
void ff() {
cout << "func1" << endl;
this_thread::sleep_for(chrono::seconds(1));
}
int main()
{
thread t1(ff);
//t1.join();
cout << "main" << endl;
return 0;
}
#endif
/*************************
example2 - detach :
那如果子线程t1的运行时间太长了,我不想让主线程阻塞等待t1运行,怎么办呢?下面就介绍detach()函数。
detach()函数的作用是将子线程和主线程分离,让子线程在后台独立运行。如果主线程运行很快,先结束运行了,子线程还未执行完的话,那么子线程会在后台继续执行,执行完毕时由线程库回收其资源。
请看如下例子,主线程中创建子线程t1,通过detach()函数,t1从主线程中分离并开始运行,输出fun1()开始 ,然后休眠1s,这个过程中主线程输出main(),然后子线程休眠结束,输出fun1()结束。
**************************/
#if 0
#include<iostream>
#include <thread>
#include <chrono>
using namespace std;
void fun1() {
cout << "fun1()开始" << endl;
this_thread::sleep_for(chrono::seconds(1)); // 休眠1秒
cout << "fun1()结束" << endl;
}
int main() {
thread t1(fun1); // 创建一个线程,名为t1,这个线程会执行fun1()函数
t1.detach();
this_thread::sleep_for(chrono::milliseconds(500)); // 休眠0.5秒
cout << "main()" << endl;
system("pause");
}
#endif
/*************************
example3 - 互斥锁mutex :
- lock_guard 没有lock和unlock接口,靠析构函数自动释放,作用域是局部空间 粒度较大
- unique_lock 有lock和unlock的接口,可以锁定代码段,粒度更小
**************************/
#if 0
#include<iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex m;
void fun1() {
lock_guard<mutex> loc(m); // 构造函数里自动加锁
cout << "fun1" << endl; // 做别的事情
// lock_guard出了这个作用域,自动解锁
}
void fun2() {
unique_lock<mutex> loc(m); // 构造函数里自动加锁
cout << "do A" << endl;
loc.unlock(); // 解锁
loc.lock(); // 重新加锁
cout << "do B" << endl;
loc.unlock(); // 解锁
}
int main() {
thread t1(fun2); // 创建一个线程,名为t1,这个线程会执行fun1()函数
t1.detach();
this_thread::sleep_for(chrono::milliseconds(500)); // 休眠0.5秒
cout << "main()" << endl;
system("pause");
}
#endif
/*************************
example4 - 条件变量condition_variable :
上述的unique_lock通常是和condition_variable配合使用的。当 condition_variable 对象的 wait() 函数被调用的时候,它使用 unique_lock来锁住当前线程。
当前线程会一直被阻塞,直到另外一个线程在相同的 condition_variable 对象上调用了notification()函数来唤醒当前线程。
**************************/
#if 0
/*
//原始版本 线程顺序容易出错
#include<iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex m;
class Foo {
public:
Foo() {}
void first() {
for (int i = 0; i < 10; ++i) {
cout << "first" << endl; // 打印10个first
}
}
void second() {
for (int i = 0; i < 10; ++i) {
cout << "second" << endl; // 打印10个second
}
}
void third() {
for (int i = 0; i < 10; ++i) {
cout << "third" << endl; // 打印10个third
}
}
};
int main() {
Foo F;
// 创建3个线程,分别调用first()、second()、third()函数打印
thread(&Foo::first, &F).detach();
thread(&Foo::second, &F).detach();
thread(&Foo::third, &F).detach();
system("pause");
}
*/
// 如何让三个线程按序打印,也就是不管哪个线程先被创建,都按照first()、second()、third()的顺序打印呢?
/*
这时候就可以用前面提到过的unique_lock和condition_variable。我们设置两个标志位,flag1为true表示允许second打印、flag2为true表示允许third打印,先初始化flag1、flag2为false。线程创建后,可能出现以下的执行顺序:
(1)如果在first()还未打印完时(或者压根还没开始执行时),执行second(),由于此时flag1被初始化为false,进入while循环,调用条件变量的wait函数,会将当前线程阻塞起来。此时需要等另一个子线程执行first()打印完毕后,将flag1设为true,然后调用notify_all()将阻塞的线程唤醒,second()才能接着执行,完成打印。
(2)如果first()已经执行完毕,然后执行second(),那么flag1为true,执行second()的那个线程不会进入while循环,也不会被阻塞起来,能顺利执行。
third()的执行先后也可参考以上两种情况。
*/
#include<iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;
class Foo {
private:
mutex m; // 互斥锁
condition_variable cd_v; // 条件变量
bool flag1; // 两个标志位
bool flag2;
public:
Foo() : flag1(false), flag2(false) {} // 初始化两个标志位为false,表示second和third还不能打印
void first() {
for (int i = 0; i < 10; ++i) {
cout << "first" << endl;
}
flag1 = true; // first执行后,将flag1置为true,表示second可以打印了
cd_v.notify_all(); // 唤醒等待队列里的所有线程
}
void second() {
unique_lock<mutex> loc(m); // 加锁
// (1)先判断flag1是否为true,true则不会进入while循环,顺利向下执行;
// (2)flag1为false则执行wait函数,释放锁,并进入对象cd_v的等待队列;
// (3)当别的线程调用对象cd_v的notify_all()函数,会唤醒在等待队列中的这个线程,然后重新获得锁,继续步骤(1)
while (flag1 == false) {
cd_v.wait(loc);
}
for (int i = 0; i < 10; ++i) {
cout << "second" << endl;
}
flag2 = true; // 修改flag2表示third已经可以打印了
cd_v.notify_all(); // 唤醒等待队列里的所有线程
// 当离开这个局部作用域时,unique_lock会调用析构函数,自动解锁
}
void third() {
unique_lock<mutex> loc(m);
while (flag2 == false) {
cd_v.wait(loc);
}
for (int i = 0; i < 10; ++i) {
cout << "third" << endl;
}
}
};
int main() {
Foo F;
// thread 需要传参,尤其对类对象,函数 + 类对象的地址
thread t1(&Foo::first, &F);
thread t2(&Foo::second, &F);
thread t3(&Foo::third, &F);
t1.detach();
t2.detach();
t3.detach();
system("pause");
}
#endif // 1
#if 0
// 题目一、交替打印字符串
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>
using namespace std;
class FooBar {
private:
int n;
mutex mtx;
condition_variable cv;
bool foo_done = false;
public:
FooBar(int n) {
this->n = n;
}
void foo(function<void()> printFoo) {
for (int i = 0; i < n; i++) {
unique_lock<mutex> locker(mtx);
while (foo_done == true)
cv.wait(locker);
printFoo();
foo_done = true;
cv.notify_one();
}
}
void bar(function<void()> printBar) {
for (int i = 0; i < n; i++) {
unique_lock<mutex>locker(mtx);
while (foo_done == false)
cv.wait(locker);
printBar();
foo_done = false;
cv.notify_one();
}
}
};
void printBar() {
cout << "bar" << endl;
}
void printFoo() {
cout << "Foo" << endl;
}
int main() {
FooBar F(4);
// thread 需要传参,尤其对类对象,函数 + 类对象的地址
thread t1(&FooBar::foo, &F,printBar);
thread t2(&FooBar::bar,&F,printFoo);
t1.detach();
t2.detach();
system("pause");
}
#endif // 1
#if 1
// 题目一、交替打印字符串
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>
using namespace std;
int n = 5;
mutex mtx;
condition_variable cv;
bool foo_done = false;
void foo(function<void(int)> printFoo) {
for (int i = 0; i < n; i++) {
unique_lock<mutex> locker(mtx);
while (foo_done == true)
cv.wait(locker);
printFoo(i);
foo_done = true;
cv.notify_one();
}
}
void bar(function<void(int)> printBar) {
for (int i = 0; i < n; i++) {
unique_lock<mutex>locker(mtx);
while (foo_done == false)
cv.wait(locker);
printBar(i);
foo_done = false;
cv.notify_one();
}
}
void printBar(int i) {
cout << "bar" <<i<< endl;
}
void printFoo(int i) {
cout << "Foo" <<i<< endl;
}
int main() {
// thread 需要传参,尤其对类对象,函数 + 类对象的地址
thread t1(foo, printBar);
thread t2(bar,printFoo);
t1.detach();
t2.detach();
system("pause");
}
#endif // 1
5. 题目2 题目3 可运行代码
// 题目2 多线程顺序执行
#if 0
#include<iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;
class Foo {
private:
mutex m; // 互斥锁
condition_variable cd_v; // 条件变量
bool flag1; // 两个标志位
bool flag2;
public:
Foo() : flag1(false), flag2(false) {} // 初始化两个标志位为false,表示second和third还不能打印
void first() {
for (int i = 0; i < 10; ++i) {
cout << "first" << endl;
}
flag1 = true; // first执行后,将flag1置为true,表示second可以打印了
cd_v.notify_all(); // 唤醒等待队列里的所有线程
}
void second() {
unique_lock<mutex> loc(m); // 加锁
// (1)先判断flag1是否为true,true则不会进入while循环,顺利向下执行;
// (2)flag1为false则执行wait函数,释放锁,并进入对象cd_v的等待队列;
// (3)当别的线程调用对象cd_v的notify_all()函数,会唤醒在等待队列中的这个线程,然后重新获得锁,继续步骤(1)
while (flag1 == false) {
cd_v.wait(loc);
}
for (int i = 0; i < 10; ++i) {
cout << "second" << endl;
}
flag2 = true; // 修改flag2表示third已经可以打印了
cd_v.notify_all(); // 唤醒等待队列里的所有线程
// 当离开这个局部作用域时,unique_lock会调用析构函数,自动解锁
}
void third() {
unique_lock<mutex> loc(m);
while (flag2 == false) {
cd_v.wait(loc);
}
for (int i = 0; i < 10; ++i) {
cout << "third" << endl;
}
}
};
int main() {
Foo F;
// thread 需要传参,尤其对类对象,函数 + 类对象的地址
thread t1(&Foo::first, &F);
thread t2(&Foo::second, &F);
thread t3(&Foo::third, &F);
t1.detach();
t2.detach();
t3.detach();
system("pause");
}
#endif
// 题目3 多线程交替打印字符
#if 0
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>
using namespace std;
int n = 5;
mutex mtx;
condition_variable cv;
bool foo_done = false;
void foo(function<void(int)> printFoo) {
for (int i = 0; i < n; i++) {
unique_lock<mutex> locker(mtx);
while (foo_done == true)
cv.wait(locker);
printFoo(i);
foo_done = true;
cv.notify_one();
}
}
void bar(function<void(int)> printBar) {
for (int i = 0; i < n; i++) {
unique_lock<mutex>locker(mtx);
while (foo_done == false)
cv.wait(locker);
printBar(i);
foo_done = false;
cv.notify_one();
}
}
void printBar(int i) {
cout << "bar" <<i<< endl;
}
void printFoo(int i) {
cout << "Foo" <<i<< endl;
}
int main() {
// thread 需要传参,尤其对类对象,函数 + 类对象的地址
thread t1(foo, printBar);
thread t2(bar,printFoo);
t1.detach();
t2.detach();
system("pause");
}
#endif // 1
6. 题目2题目3 写法2
6.1 三个线程顺序打印
// 三个线程 顺序打印
#if 1
condition_variable cv;
mutex m;
int flag = 0;
int n = 10;
void printA() {
unique_lock<mutex> loc(m);
for (int i = 0; i < n; i++){
cout << "A:" << i << endl;
}
flag = 1;
loc.unlock();
cv.notify_all();
}
void printB() {
unique_lock<mutex> loc(m);
while (flag != 1)
cv.wait(loc);
for (int i = 0; i < n; i++) {
cout << "B:" << i << endl;
}
flag = 2;
loc.unlock();
cv.notify_all();
}
void printC() {
unique_lock<mutex> loc(m);
while (flag != 2)
cv.wait(loc);
for (int i = 0; i < n; i++) {
cout << "C:" << i << endl;
}
loc.unlock();
cv.notify_all();
}
int main()
{
thread t1(printA);
thread t2(printB);
thread t3(printC);
t1.detach();
t2.detach();
t3.detach();
system("pause");
return 0;
}
#endif // 1
6.2 三个线程交替打印
// 三个线程 交替打印
#if 0
condition_variable cv;
mutex m;
int flag = 0;
int n = 10;
void printA() {
for (int i = 0; i < n; i++) {
unique_lock<mutex> loc(m);
while (flag != 0) cv.wait(loc);
cout << "A:" << i << endl;
flag = 1;
loc.unlock();
cv.notify_all();
}
}
void printB() {
for (int i = 0; i < n; i++) {
unique_lock<mutex> loc(m);
while (flag != 1) cv.wait(loc);
cout << "B:" << i << endl;
flag = 2;
loc.unlock();
cv.notify_all();
}
}
void printC() {
for (int i = 0; i < n; i++) {
unique_lock<mutex> loc(m);
while (flag != 2) cv.wait(loc);
cout << "C:" << i << endl;
flag = 0;
loc.unlock();
cv.notify_all();
}
}
int main()
{
thread t1(printA);
thread t2(printB);
thread t3(printC);
t1.detach();
t2.detach();
t3.detach();
system("pause");
return 0;
}
#endif // 1