学习C++并发编程笔记 - 线程管理

本文详细介绍了C++中线程的创建、管理,包括使用std::thread构造函数、函数指针、函数对象、lambda表达式,以及线程守护者thread_gaurd的使用,还涉及线程的join、detach操作,参数传递的注意事项,以及资源获取初始化(RAI)的应用。
摘要由CSDN通过智能技术生成

没来得及整理~

//
//  day1.cpp
//  LearnC++MultipleCode
//
//

#include "day1.hpp"
#include <list>
#include <memory>
/*
 1. 线程管理
 */
namespace day1 {
//1. 构造方法生成 std::thread ,参数,可调用类型构造
//函数指针
void doSomething() {
    std::cout << "get_id() = " <<  std::this_thread::get_id() << std::endl;
    std::cout<< "day1 doSomething ....."<<std::endl;
}

//函数对象,提供的函数对象会复制到新线程的存储空间当中,函数对象的执行和调用都在线程的内存空间中运行
class DoSomethingElse {
public:
    void operator()() {
        std::cout<< "day1 Do Something Else ....."<<std::endl;
    }
};

//lambda表达式
auto doSomethingMore = []() {
    std::cout<< "day1 doSomething More ....."<<std::endl;
};
template<class Y>
class X {
public:
    void do_lengthy_work(){
        std::cout << "do_lengthy_work " << std::endl;

    }
    void do_lengthy_work1(int a){
        std::cout << "do_lengthy_work , before a = " << a << std::endl;
        a = 3;
        std::cout << "do_lengthy_work , after a = " << a << std::endl;
    }
};

class thread_gaurd {
public:

    //书上加了explicit,但是不用好像也可以
    thread_gaurd(std::thread &t):t_(t) {}
    ~thread_gaurd() {
        if (t_.joinable()) {
            std::cout << "t_.join();" << std::endl;
            t_.join();
        }
    }
    thread_gaurd(const thread_gaurd &tg) = delete;
    thread_gaurd& operator=(const thread_gaurd &g) = delete;
private:
    std::thread &t_;
};

void day1Test() {
    std::thread t1(doSomething);
//    std::thread t2((DoSomethingElse())); //注意⚠️:这里并不是构造一个临时对象,更像是一个函数声明,圆括号作为函数声明消除了歧义;这里相当与声明了一个名为t1的函数,这个函数带有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个 std::thread 对象的函数,而非启动了一个线程。
    DoSomethingElse e;
    std::thread t2(e);
    std::thread t3((DoSomethingElse()));
    //这个时候是不是看出了初始化列表的好处呢?
    std::thread t4{DoSomethingElse()};
    std::thread t5(doSomethingMore);
    
/*
 //注意⚠️:要学会分离或者等待,否则会抛出异常'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1817e23ec),如果 std::thread 对象销毁之前还没有做出决定,程序就会终止 ( std::thread 的析构函数会调用 std::terminate() )。
 //注意⚠️: 因此,即便是有异常存在,也需要确保 线程能够正确的加入(joined)或分离(detached)。
 
 */
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();

    
    //注意⚠️: join只能一次,之后调用要用joinable()判断
    
    //如何使得线程在即使有异常也能正常退出?使用RAII,Resource Acquisition Is Initialization 资源获取即初始化方式,包装余个类,在析构函数中使用join()
    std::thread t6(doSomething);
    thread_gaurd tg{t6};
    std::thread t7(doSomething);
    thread_gaurd tg1 = t7;
//    thread_gaurd tg2 = std::thread(doSomething);//No viable conversion from 'std::thread' to 'day1::thread_gaurd' 为什么这个不可以???难道是因为引用的不能是个临时的值?
    
    /*
     分离, detach
     */
    //注意⚠️: 判断一个线程可否detach的方式也是joinable(),join的线程不能再detach,且不能对未启动的线程join()或者detach,虽然听着不太可能
    std::thread t8{[](){
        std::cout << "t8 dosomething" << std::endl;
    }};
    t8.detach();
    
    /*
     线程传递参数
     */
    //传递参数实际上就是需要拷贝到独立内存中。
    //注意不要传递指针过去,你访问的对象可能会不存在了。
    int arr[5]{1,2,3,4,5};
    char *c{"1111111"};
    std::thread t9{[](int size, int *arr){
        std::cout<< "t9 wait a minite ~" << std::endl;
        std::cout<< "t9 wait a minite ~" << std::endl;
        std::cout<< "t9 wait a minite ~" << std::endl;
        std::cout<< "t9 wait a minite ~" << std::endl;
        std::cout<< "t9 wait a minite ~" << std::endl;
        
        if (arr[size - 1] == 5) {
            std::cout<< "a = "<< (size == 0 ? -1 : arr[size-1]) << std::endl;
        } else {
            
            std::cout<< "oh , you pass a pointer which is a arr, now it is abnormal !" << std::endl;
        }
    }, 5, arr};
    
    t9.detach();
    
    //程序会输出 oh , you pass a pointer which is a arr, now it is abnormal !
    
    
    /*
     传递一个参数是引用怎么做,实际上最好不要传引用,很危险......,我现在想实现
     但是要知道线程只会盲目的拷贝值,所以你传递一个变量,实际上是副本,所以要用std::ref
     */
    int a = 0;
    std::thread t10{[](int &a) {
        std::cout<< "hh , a is passed as a ref, a = " << a << " and, i will change it to 10000000. " << std::endl;
        a = 10000000;
    }, std::ref(a)};
    t10.join();
    std::cout<< "hh , after join t10 a =  " << a << std::endl;
    
    /*
     hh , a is passed as a ref, a = 0 and, i will change it to 10000000.
     hh , after join t10 a =  10000000
     */
    
    /*
     还可以传递对象成员函数,对象地址作为指针对象 提供给函数
     */
    
    std::list<int> lst1{7,2,6,1,2,3,4,5};
    std::list<int> *list_ptr = &lst1;
//    list_ptr->sort();
    std::thread t11{&std::list<int>::clear, list_ptr};
    //为什么下面方法不行?
//    std::thread t13{&std::list<int>::sort, list_ptr};

    t11.join();
//    t13.join();
    std::cout << "lst1 = " << std::endl;
    for (auto it = lst1.begin(); it != lst1.end(); ++it) {
        std::cout << *it << std::endl;
    }

    X<int> my_x;
    std::thread t12(&X<int>::do_lengthy_work,&my_x); // 1
    t12.join();
    
    X<int> my_x1;
    int a1 = 1;
    std::thread t14(&X<int>::do_lengthy_work1,&my_x1, a1); // 1
    t14.join();
    std::cout << "a1 = " << a1 <<std::endl;
    
    
    /*
     转移线程所有权
     */
    /* 首先要理解的是,为什么std::thread是管理一个线程?
    1. 它只能管理一个线程
    2. 它不能被拷贝,也就是它管理的线程不能被拷贝
    3. 我们所使用的移动拷贝构造实际上是把线程交给了别人,当前线程的管理者没事做了,又可以去管理其他线程
     4. 但是注意如果你正在管理一个线程,那么你不能再管理其他线程,看看下面例子
     */
    
    std::thread t21(doSomething);
    std::thread t22{std::move(t21)};
    //也就是说可以赋值,但前提是thread当前没有管理线程
    t21 = std::thread(doSomething);
//    t21 = std::move(t22);
    /*
     挂了
     thread& operator=(thread&& __t) _NOEXCEPT {
         if (!__libcpp_thread_isnull(&__t_))
             terminate(); //挂在了这里
         __t_ = __t.__t_;
         __t.__t_ = _LIBCPP_NULL_THREAD; //这里会置为空线程?
         return *this;
     }
     */
    t21.join();
    t22.join();
    
    /*
     同样也就可以作为函数参数传递了,用移动拷贝
     */
    auto createAThread = []()-> std::thread {
       
        std::cout << "createAThread" << std::endl;
        std::thread t(doSomething);
//        return std::move(t);//Moving a local object(局部对象) in a return statement prevents copy elision.copy elision(就是不copy了,直接在函数调用的地方构造), 这个对象会默认采用移动拷贝给t23!。
        return t;
    };
    std::thread t23 = createAThread();
    std::cout << "t23::get_id()1 = " << t23.get_id() << std::endl;
    t23.join();
    
    std::thread t24 = std::move(t23);
    std::cout << "std::this_thread::get_id() = " << std::this_thread::get_id() << std::endl;
    
//    t24.join(); //报错,此时其实t23yijoinable,不能再joinable了,故你在接受移动过来的对象线程时一定要判断是否可以joinable()
    
    /*
     运行时决定线程数量std::thread::hardware_concurrency()
     线程标识类型为 std::thread::id
     第一种,可以通过调 用 std::thread 对象的成员函数 get_id() 来直接获取。如果 std::thread 对象没有与任何执 行线程相关联, get_id() 将返回 std::thread::type 默认构造值,这个值表示“无线程”。
     第二 种,当前线程中调用 std::this_thread::get_id() (这个函数定义在 <thread> 头文件中)也可以 获得线程标识。
     */
    
    /*
     day1 Do Something Else .....day1 doSomething .....day1 doSomething More .....

     day1 Do Something Else .....

     day1 Do Something Else .....
     day1 doSomething .....
     day1 doSomething .....
     t8 dosomething
     t_.join();
     t9 wait a minite ~t_.join();

     t9 wait a minite ~
     t9 wait a minite ~
     t9 wait a minite ~
     t9 wait a minite ~
     oh , you pass a pointer which is a arr, now it is nullptr !
     main ()
     */
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值