C++快速讲解(十):线程


前言:主要介绍了线程的创建、join和detach、线程传递参数、获取线程id、结束线程、并发访问、线程同步等。


1.创建线程

#include <iostream>
#include <thread>

using namespace std;
//创建线程

void show(){
    for (int i = 0; i < 10; ++i) {
        cout<<"---i:"<<i<<endl;
    }
}
int main() {
    thread t(show);
    cout<<"执行了main函数"<<endl;
    return 0;
}

在这里插入图片描述

2.join 和 detach

2.1 join

join 的意思是让主线程等待子线程执行结束后,在进行下一步,意思是让主线程挂起

#include <iostream>
#include <thread>
#include <windows.h>

using namespace std;

void show(){
    for (int i = 0; i < 10; ++i) {
        cout<<"i:"<<i<<endl;
        Sleep(500);
    }
}
int main() {
    //让这个线程执行上面的show 函数
    thread t(show) ;

    //让主线程等待子线程运行结束后,再继续下面的逻辑
    //否则主线程运行结束,程序就结束了。
    t.join();
    cout << "执行了main函数 " <<endl;
    return 0;
}

在这里插入图片描述

2.2 detach

detach的意思将本线程从调用线程中分离出来,允许本线程独立执行,从此和主线程再也没有任何关系。

#include <iostream>
#include <thread>
#include <windows.h>

using namespace std;

void show(){
    for (int i = 0; i < 10; ++i) {
        cout<<"i:"<<i<<endl;
        Sleep(500);
    }
}
int main() {
    thread t(show) ;
    t.detach();
    cout << "执行了main函数 " <<endl;
    return 0;
}

3.传递参数

往线程里面执行的函数传递参数,最长使用的办法就是bind机制 , 这里以在线程内部构建学生对象,从外部传递姓名和年纪数据。

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

using namespace std;
//传递参数
class Student{
public:
    string name;
    int age;
    Student(string name,int age):name(name),age(age){
        cout <<"执行构造函数了~"  << name <<" = "<< age<< endl;
    }
};

void constructor(string name ,int age ){
    cout <<"执行构造学生的工作" << endl;
    Student s(name ,age);
}

int main() {
    thread t(bind(constructor,"fly",18));
    return 0;
}

在这里插入图片描述

4.获取线程id和休眠

  • 每一个线程在执行的时候,都有自己的一个标识id, 只有在少数情况下,线程的id会变得与众不同。通过 t.get_id() 获取线程对一个的id , 也可以使用get_id() 获取当前线程的 id 。
  • 让线程休眠,等待一段时间然后继续执行,这样的场景在开发的时候经常会出现,在 c++中,让线程休眠,如果是在windows可以使用 windows.h头文件中的Sleep函数 , 如果是linux 系统,可以使用#include里面的usleep函数 或者 也可以使用 this_thread:: 里面的 sleep_for 函数
#include <iostream>
#include <thread>
#include <windows.h>

using namespace std;
// 获取线程id
void show(){
    for (int i = 0;i<10;i++){
        Sleep(500);//休眠
        cout<<"i---->"<<i<<endl;
    }

}

int main() {
    cout <<"主线程的id:"<< this_thread::get_id<< endl;
    thread t(show);
    cout<<"t.get_id():"<<t.get_id()<<endl;
    t.join();
    return 0;
}

在这里插入图片描述

5.结束线程

线程的退出,手段还是很多的,但是万般手段中,建议使用return。

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

using namespace std;

//结束线程
void show1(){
    for (int i = 0; i < 25; ++i) {
        if(i== 3){
            cout <<"函数返回,线程终止。" << endl;
            return ; //或者在这抛出异常,也形同return。
        }
    }
}

int main() {
    thread t(show1);
    return 0;
}

在这里插入图片描述

6.并发访问

由于cout对象并不会产生互斥,所以在多线程场景下,输出的结果并不是我们想要的,显得杂乱无章。这时候可以使用mutex 来控制互斥

#include <iostream>
#include <thread>
#include <windows.h>
#include <mutex>
using namespace std;
//并发访问

mutex mutext1;
void show(){
    int co = 10;
    while(co>0){
        mutext1.lock();//上锁
        cout<<"show_thread:"<<this_thread::get_id()<<endl;
        co--;
        mutext1.unlock();
        Sleep(500);
    }
}

int main() {
    thread t1(show);
    thread t2(show);
    t1.join();
    t2.join();
    return 0;
}

在这里插入图片描述

7.线程同步

如果有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作 , 或者同时写入 ,就会导致变量值或数据出现混乱,从而导致程序异常。

7.1 使用互斥量处理同步

#include <iostream>
#include <thread>
#include <functional>
#include <windows.h>
#include <mutex>
#include <queue>

using namespace std;
//线程同步 使用互斥量处理同步

class Box{};
//互斥变量
mutex mutex1;

//队列存放盒子
queue<Box*> q;
//初始化盒子
void init(){
    for (int i = 0; i < 10; ++i) {
        q.push(new Box());
    }
}

//从对列中取出盒子
void moveBox(string name){
    mutex1.lock();
    if(!q.empty()){
        Sleep(1000);
        q.pop();
        cout<<name<<"搬走了一个盒子现在还剩"<<q.size()<<endl;
    }
    mutex1.unlock();
}

void fun1(){
    while(1){
        moveBox("fun1");
        Sleep(1000);
    }
}

void fun2(){
    while(1){
        moveBox("fun2");
        Sleep(700);
    }
}

int main() {
    init();
    thread t1(fun1);
    thread t2(fun2);

    //阻塞主线程
    t1.join();
    t2.join();
    return 0;
}

在这里插入图片描述

7.2 lock_guard

一般来说不建议直接调用mutex 的成员函数lock 或者 unlock 来执行加锁解锁的操作,这要求程序员必须准确的知道在什么位置进行解锁操作。c++ 提供了一个模板类 lock_guard ,可以对mutex进行包装,在执行lock_guard的构造时进行加锁操作,执行lock_guard析构时进行解锁操作

#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
using namespace std;

mutex mutex1;
void fun(){
    int count=10;
    while(count>0){
        //执行这句话,即上锁,等本次循环结束,会自动释放锁
        lock_guard<mutex> lg(mutex1);
        cout<<"fun的thread:"<<this_thread::get_id()<<endl;
        cout<<"count:"<< count<<endl;
        count--;
        Sleep(1000);
    }
}

int main(){
    thread t1(fun);
    thread t2(fun);
    t1.join();
    t2.join();
    return 0 ;
}

在这里插入图片描述

7.3 unique_guard

unique_guard 拥有 lock_guard的所有功能,并且内部还提供了加锁和解锁的操作,以便对加锁的粒度进行细化,而 lock_guard 的加锁范围通常是一个范围区域(比如函数) 。unique_lock 对于锁的管理比较灵活.它不像lock_guard 一样.必须要求他管理的锁在他初始化的时候必须加锁.而是可以自己灵活的.想加锁.就加锁.

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <windows.h>
using namespace std;

mutex mutex1;

void fun(){
    int count=10;
    while(count>0){
        //执行这句话,即上锁,等本次循环结束,会自动释放锁
        unique_lock<mutex> ul(mutex1);
        cout<<"fun的thread:"<<this_thread::get_id()<<endl;
        cout<<"count:"<< count<<endl;
        count--;
        ul.unlock(); //可以手动释放锁
        Sleep(500);
    }
}

int main() {
    thread t1(fun);
    thread t2(fun);
    t1.join();
    t2.join();
    return 0;
}

在这里插入图片描述

7.4 条件变量

条件变量时从 condition_variable 直接翻译过来的,条件变量可以很好的管理多线程的并发操作。条件变量可以让线程达到某个条件的时候进入等待状态, 当条件变成对立面的时候线程继续执行。

7.4.1 简单使用

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <windows.h>
#include <condition_variable>
using namespace std;

condition_variable cv;
mutex m;

void fun1(){
    for(int i = 1;i<100;i++){
        Sleep(1000);
        lock_guard<mutex> lk(m);
        cout << "func1 ----  i = " << i << endl;
        if(i%10 == 0){
            cout << "func1当前i能整除10 ,通知func1运行" << endl;
            cv.notify_one();
        }
    }
}

void fun2(){
    for(int i = 1;i<100;i++){
        unique_lock<mutex> ul(m);
        cout << "func2 ----  i = " << i << endl;
        if(i%5 == 0){
            cout << "func2当前i能整除5 ,通知func2运行" << endl;
            cv.wait(ul);
        }
    }
}


int main(){
    thread t2(fun2);
    thread t1(fun1);
    t1.join();
    t2.join();
    return 0 ;
}

在这里插入图片描述

7.4.2 条件变量实现搬运盒子

#include <iostream>
#include <thread>
#include <string>
#include <condition_variable>
#include <deque>
#include <windows.h>

using namespace std;

mutex m;
condition_variable cv;

class Box{};

//模拟工作台
deque <Box *> queue;

//左边的手臂把盒子搬上去
void leftMoveIn(){
    while(1){
        Box *box = new Box();
        queue.push_back(box);
        cout << "左手搬来一个,还剩:" << queue.size() << endl;
        cv.notify_all();
        Sleep(3000);
    }
}

void rightMoveOut(){
    while(1){
        unique_lock<mutex> ul(m);
        if(!queue.empty()){   //无限循环状态的轮询.
            queue.pop_back();
            cout << "右手搬走一个,还剩:" << queue.size() << endl;
            Sleep(1000);
        }else{
            //表示处于空的状态.等
            cout <<"没盒子了,右手等待"<<endl;
            cv.wait(ul);
        }
    }
}

//初始化盒子
void init(){
    for (int i = 0; i < 5; ++i) {
        queue.push_back(new Box);
    }
}

int main() {
    init();
    thread leftHand(leftMoveIn);
    thread rightHand(rightMoveOut);
    leftHand.join();
    rightHand.join();
    return 0;
}

在这里插入图片描述

7.5 async 函数

一般来说,函数如果在线程内部执行,当函数执行完毕后,想要获取到函数的返回值。除了在线程执行结束后,通过改变中间变量的方式之外,没有更好的办法了。 async 函数除了兼备thread的功能之外,还可以获取到函数的返回值。

7.5.1 简单使用

#include <iostream>
#include <future>


using namespace std;

int add(int a , int b){
    return  a + b;
}

int main() {
    //async背后会开启线程,用于运行add函数 <int> 表示函数的返回值类型
    future<int> future = async(add, 3, 4);

    //获取add函数的返回结果,get()会阻塞主线程,知道函数执行结束
    int result = future.get();

    cout << "result = " << result << endl;
    return 0;
}

7.5.2 启动策略

默认情况下,async函数背后不一定会执行创建新的线程执行函数,也有可能在当前线程上运行指定的函数。这是由于它的启动策略决定,可以通过函数的第一个参数指定。

//默认的启动策略
auto fut1 = std::async(f); // run f using default launch policy

//上面的代码等同于这一行。
auto fut2 = std::async(std::launch::async|std::launch::deferred,f);

7.5.3 异步操作

在async函数的第一个参数传递 launch::async 即可确保函数是异步执行

#include <iostream>
#include <future>
#include <unistd.h>

using namespace std;

void printI(){
    for (int i = 0; i < 10; ++i) {
        cout << "i == " << i << endl;
        usleep(1000 * 1000 * 1);
    }
}

int main() {
    //表示使用异步执行add函数
    async(launch::async ,printI );

}

7.5.4 同步操作

不管是否开启新的线程,只要调用了wait() 或者 get() 都会让函数运行,并且主线程陷入阻塞状态。wait()表示阻塞主线程,不会有结果被返回。get() 的底层显示调用了wait,接着获取到函数的结果。

#include <iostream>
#include <future>
#include <unistd.h>

using namespace std;

void printI(){
    for (int i = 0; i < 10; ++i) {
        cout << "i == " << i << endl;
        usleep(1000 * 1000 * 1);
    }
}

int main() {
    //表示使用异步执行add函数
    future<void> f = async(launch::async ,printI );
    f.get();
} 

//或者是:
int main() {
    //deferred: 表示延迟、推迟。 函数不会执行,除非后续调用get() 或  wait()
    future<void> f = async(launch::deferred ,printI );
    f.get();
    //f.wait();

    return 0 ;
}

结束!!!

  • 16
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

等待着冬天的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值