• C++ thread库


    C++11中提供了thread线程库,它本质上和pthread库差不多,只不过被封装了,同时它还是可以跨平台的

    thread

    构造函数

    thread() noexcept;
    template <class Fn, class... Args>
    explicit thread (Fn&& fn, Args&&... args);
    
    • 1
    • 2
    • 3

    注意

    1. 无参构造就只是开一个线程,但是不会工作,不会执行
    2. thread的第一个元素是可调用对象
    • lambda表达式
    • 函数指针
    • 类对象的仿函数
    1. 后面的是可变参数列表,可以传入任意的参数
    2. 第一个参数是一个模板参数,所以它是万能引用,既可以传左值也可以传右值

    函数指针

    void print(int x,int y)//线程函数
    {
    	cout<<x<<y<<endl;
    }
    
    
    void threads()
    {
    
        //线程库
        thread t1;//这里可以创建一个无参的,即线程不执行
        thread t2(print, 10,2);//第一个参数因为是模板,所以它是一个万能引用,func它不一定是一个函数,可调用对象就可以了,即可以传左值也可以传右值
        //第二个参数是可变的模板参数,可以传0-n个参数
        //第一个可以
        t2.join();//这和c的线程库也是一样的
        
        //不像c语言要使用一个结构体传进去,
        //原来在c语言我们是使用线程id来控制这些线程的
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Lambda表达式

    int main()
    {
    	thread t([](){cout<<"hello world"<<endl;});//使用lambda表达式进行传参
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int main()
    {
    	int x = 0;
        mutex mtx;
        int N = 10000;
        atomic<int> costtime1(0);
        thread t1([&]
                  {
            int begin1 = clock();
            mtx.lock();
            for (int i = 0; i < N; i++)
            {
                x++;
            }
            mtx.unlock();
            cout<<x<<endl;
            int end1 = clock();
            costtime1 += (end1 - begin1);
            cout<<costtime1<<endl;});//lambda表达式可以去处理一些小函数
        cout << x << ":" << costtime1 << endl;
    
        // costtime1就是调用花费的时间 }); //这里用一个可调用对象就可以了,我们这里用lambda表达式,&全部捕获 }); });
        //项目里面,我们还是用原子的,相对更好一点
    
        int costtime2 = 0;
        thread t2([&]
                  {
                      int begin2 = clock();
            mtx.lock();
    
                      for (int i = 0; i < N; i++)
                      {
                          x++;
                      }
            mtx.unlock();
                      int end2 = clock();
                      costtime2 = end2 - begin2; }); // costtime1就是调用花费的时间 });
        t1.join();
        t2.join();
        cout << x << ":" << costtime1 << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    仿函数

    class Func
    {
    
    void operator()
    {
            cout << std::this_thread::get_id() << "++x " << x << endl; //获得对应的线程id,这里是一个结构体,因为它可以跨平台
    }
    int main()
    {
    	Func fc;
    	thread ss(fc);//使用类对象
    	thread s((Func()));//使用仿函数进行传参,就要这样弄,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用类对象成员函数

    • 静态成员函数可以 直接使用对应的静态函数
    • 使用普通成员函数,除了 有成员函数还要有类对象 ,类对象可以是一个临时对象
    class test
    {
    public:
        test()
        {
        }
        ~test()
        {
        }
        static void do_work1()
        {
            cout << "do work 1" << endl;
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
        void do_work2()
        {
            cout << "do work 2" << endl;
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
        void do_work3(string& arg, int x, int y)
        {
            cout << "do work 3 x=" << x << "y=" << y << endl;
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
    };
    
    void test_thread(int &data)
    {
        cout << "thread data=" << data << endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
    int main()
    {
        int data=10;
        thread t1(&test_thread,ref(data));
        thread t2(&test::do_work1);//静态成员函数可以直接使用对应的静态函数
        test t;
        thread t3(&test::do_work2,t);//使用普通成员函数,除了有成员函数,还要有类对象
        thread t3(&test::do_work3,t,std::ref("test"),10,20);//使用普通成员函数,除了有成员函数,还要有类对象
      
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    拷贝构造

    thread线程库不允许进行拷贝构造,所以直接把它给删除掉

    thread (const thread&) = delete;
    
    • 1

    赋值重载

    不允许赋值一个左值对象,类比拷贝构造
    但是可以用一个右值对象来赋值

    thread& operator= (thread&& rhs) noexcept;
    copy [deleted] (2)	
    thread& operator= (const thread&) = delete;
    
    • 1
    • 2
    • 3
    void test()
    {
        int n;
        cin >> n;
        vector<thread> vthds;
        vthds.resize(n);//提前开好n个线程
    
        //现在有任务来了,我们要让这些线程都跑起来
        for (auto& e : vthds)
        {
            e = thread(print,100,2);//这里构造一个匿名对象赋值给它,这个地方又利用了一个移动赋值,把右边的这个临时
            //对象传过去给e去执行,出了作用域就销毁了
            //右边是一个右值
            //这里的线程不支持拷贝构造,把一个线程拷贝给另一个线程,所以直接delete掉了
            //线程也不支持赋值,但是可以支持移动赋值
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    获取id

     std::this_thread::get_id()
    
    • 1
    class Func
    {
    
    void operator()
    {
            cout << std::this_thread::get_id() << "++x " << x << endl; //获得对应的线程id,这里是一个结构体,因为它可以跨平台
    }
    int main()
    {
    	thread s((Func()));//使用仿函数进行传参,就要这样弄,
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    sleep

    std::this_thread::sleep_for(std::chrono::milliseconds(100));//这里面是休眠的时间
    
    • 1

    join和detach

    • join :就是在一个线程还没处理完之前,主线程都要一直等着这个线程做,直到新线程处理完了,才会放开主线程,
    • detach: 就是会把主线程和新线程分离开来,新线程的事情不影响主线程做事,后台自动回收
    
    void print(int x,int y)//线程函数
    {
    	cout<<x<<y<<endl;
    }
    
    int main()
    {
    	thread s(print,10,20);
    	s.detach();
    	if(s.joinable())//判断是否可以被join,如果detach和join之后就不能被join
    	
    	s.join();
    	
    	cout<<"hello"<<endl;//使用join要等新线程处理完才会打印
    	
    		
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    引用与传参

    假如说我们在main函数里面定义了对象想要传到thread里面

    • 可以使用指针进行传参
    • 不能用左值来进行接收,但是可以使用std::ref( ),之后就能用左值接收
    
    void func(int* x)//用指针肯定是可以的
    {
        *x+=10;
    }
    void func(int &x) //绝对不能传左值引用,但是下面传参是用std::ref()就能接收,因为正常thread里面都是拷贝
    {
        x += 10;
    }
    
    
    int main()
    {
    	int n = 10;
        // 严格来说thread的参数不能是左值引用,
        
        thread t1(func,&n);//这样子对n的加,不可以,传值拷贝
        thread t2(func, std::ref(n)); //这样弄就可以了
        t1.join();
        t2.join();
        cout << n << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    atomic

    为了解决内置类型传参过去的线程安全的问题

    atomic s;
    
    • 1
    atomic<int> x(0); //这样对x的操作就变成了原子操作,不能用=
    atomic_long m{0};//这两者是一样的
    atomic<long> n(2);
    
    
    void func()
    {
    	x++;//因为这里的x是atomic原子变量,所以是线程安全的,
    }
    int main()
    {
    	thread s(func);
    	thread p(func);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    void threadpool()
    {
        //实现一个线程池
        atomic<int> x(0);
        //我们实现一个n个线程都对它进行加m次
        int n, m;
        cin >> n >> m;
        vector<thread> vthds;
        vthds.resize(n); //我们直接就开n个线程,用thread的默认构造函数进行初始化,无参的,就不是不运行
        //这里还有还可以用移动构造和移动赋值
        atomic<int> costtime(0);
        for (size_t i = 0; i < vthds.size(); i++)
        {
            vthds[i] = thread([m, &x, &costtime]()
                              {
                int begin=clock();
                for(int i=0;i<m;i++)
                {
                    x++;//这里的x是原子变量
                }
                int end=clock();
                costtime+=(end-begin); }); //这里我们用了移动赋值,构造了一个线程对象,线程里面用的是lambda表达式
        }
        for (auto &e : vthds)
        {
            if (e.joinable()) //判断是否可被join,
                e.join();     //这里必须要用&,如果不用的话,就会去掉拷贝构造,这是不允许的
        }
        cout << x << endl;
        cout << costtime << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    mutex

    线程安全里面的锁资源

    • lock就把临界区锁住了
    • unlock可以把解锁
    • try_lock:如果这个锁已近被别人用了,就啥也不干直接返回,如果这个锁是空闲的,就把对应的线程给锁住
    int x = 0;
    mutex mtx; //定义一个锁出来
    void Func(int& n)
    {
        //每个线程都有自己的栈,各自在执行自己的func,
        mtx.lock();
        //不能放在里面,放在里面的话,每一次都要去竞争这个锁资源,
        //加在外面变成了串行,运行,就没有意义了,理论上应该加在里面,这样就能交替并行运行
        for (int i = 0; i < n; i++)
        {
            //放在这里锁的事情和释放也有消耗,
            //对用户态的切换,要保存上下文
    
            cout << std::this_thread::get_id() << "++x " << x << endl; //获得对应的线程id,这里是一个结构体,因为它可以跨平台
    
            //抢到锁的人执行的指令太少了,导致另一个人刚离开回去休息又回来了,而是在这里循环等待,一直问,好了我就进去执行,(自旋锁)
            ++x;
        }
        mtx.unlock();
    }
    
    int main()
    {
    	int n=10;
    	thread th(Func,std::ref(n));
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    lock_guard

    我们使用锁会出现一种情况,一把锁锁住之后,但是里面就调用throw,抛异常之后,就会到catch里面,就把后续代码都跳过了,这个就会造成死锁的问题

    所以我们就可以用一个RAII机制的锁,在调用的时候构造,上锁,在析构的时候解锁

    lock_guard 只能在作用域结束后才能解锁

    模拟实现lock_guard

    template <class Lock>
    class LockGuard
    {
    private:
        Lock& _lock;//&,const,和没有默认构造函数的变量,都必须在初始化列表进行初始化
    
    public:
        LockGuard(Lock& lock)//在构造函数的时候就行加锁,但是互斥锁是不支持拷贝的,也要保持是同一把锁
        :_lock(lock)//这里的_lock是mtx的别名
        {
            _lock.lock();
        }
        ~LockGuard()
        {
            _lock.unlock();//在析构函数的时候进行解锁
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    unique_lock

    和lock_guard类似,但是可以支持在作用域结束之前解锁
    所以更加推荐使用unique_lock

    void vfunc(vector<int> &vt, int x, int base, mutex &mtx)
    {
        try
        {
            /* code */
            if (base == 200)
            {
                //对应第一个线程就让他sleep一下
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
    
            for (int i = 0; i < x; i++)
            {
                //用IO把速度降下来
                // mtx.lock(); //这样用锁有问题
                // LockGuard lock(mtx);//在这个里面就加锁,出了for作用域就解锁了,抛异常也算出了作用域,也解锁了,调用析构函数,生命周期到了
    
                //lock_guard lock(mtx);//这个是库里面提供的
    
                unique_lock<mutex> lockk(mtx);//这个效果也是一样的,除了提供构造和析构,中途解一下锁
    
                //这个push失败之后就会抛异常
    
                vt.push_back(i); //有线程安全的问题
    
                //抛异常之后unlock就不会被执行了,这样可能在上面push里面开空间也会出现问题,所以我们这里的锁可以写一个对象锁
                if (base == 100 && i == 3)
                    throw bad_alloc();
                //这里就死锁了,
                // mtx.unlock();
    
                //会出现死锁,在
            }
        }
        catch (const std::exception &e)
        {
            std::cerr << e.what() << '\n';
            //捕捉到异常之后,把锁释放掉
            // mtx.unlock();
        }
    }
    
    void test()
    {
        thread t1, t2;
        vector<int> vt;
    
        //两个线程要用同一个锁
        mutex mtx;
        //这里用的匿名对象,右值引用,线程要放在里面抛异常
        t1 = thread(vfunc, std::ref(vt), 5, 100, std::ref(mtx));  //这样是存在线程安全问题
        t2 = thread(vfunc, std::ref(vt), 10, 200, std::ref(mtx)); //
        //这种小程序用lambda就行了
    
        t1.join();
        t2.join();
    
        for (auto e : vt)
        {
            cout << e << " ";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    cond_variable

    wait

     template <class Predicate>
     void wait (unique_lock<mutex>& lck, Predicate pred);
    
    • 1
    • 2

    wait后面的参数是可调用对象,同理,也是函数指针,lambda表达式,仿函数,当返回为true时,才会唤醒,否则一直阻塞着

    notify_one:唤醒一个线程

    实战
    交替打印奇偶数

    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    //两个线程交替打印,一个打印奇数,一个打印偶数
    void test1()
    {
        int n = 100;
        // t1打印奇数
        int i = 0;
        mutex mtx;
        bool flag = false;
        condition_variable cv;
        thread t1([&]()
                  {
            while(i<n)
            {
                
                //尽量不要单独用lock和unlock
                // lock_guard lock(mtx);//这个是出了作用域才解锁
                unique_lock<mutex> lock(mtx);
    
                //wait后面的是可调用对象,函数,lambda,仿函数
                // cv.wait(lock,[&flag](){return flag;});//在里面如果是false,就会一直阻塞,直到变成true才会开始,唤醒之后flag为true,就打印,
                //这里的wait是直到条件为真才会去执行任务
                cv.wait(lock,[&](){return i%2==1;});
                //唤醒和里面条件都会挡住它
                cout<<this_thread::get_id()<<"->"<<i<<" "<<endl;
                i++;
                flag=!flag;
                cv.notify_one();//唤醒一个
    
            } });
        // t2打印偶数
        thread t2([&]()
                  {
               while(i<n){
                unique_lock<mutex> lock(mtx);
                //!flag是true,这里获取到不会阻塞,就会运行了
                // cv.wait(lock,[&flag](){return !flag;});
                cv.wait(lock,[&](){return i%2==0;});
                cout<<this_thread::get_id()<<"->"<<i<<" "<<endl;;
                i++;
                flag=!flag;//保证下一个自己不会打印
                cv.notify_one();//唤醒
               } });
        t1.join();
        t2.join();
    }
    
    int main()
    {
        test1();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    从零玩转人脸识别
    15-Groovy-日期和时间
    python数组处理方法
    中国汉服行业发展深度调研与未来趋势预测报告
    ConstantPool::allocate记录
    【SpingBoot】详细介绍SpringBoot项目中前端请求到数据库再返回前端的完整数据流转,并用代码实现
    【每日一题】最长平衡子字符串
    数字化转型孕育而来的在线文档协同工具:Baklib知识库及帮助中心
    你需要偷偷珍藏的java兼职平台
    民国漫画杂志《时代漫画》第31期.PDF
  • 原文地址:https://blog.csdn.net/m0_61567378/article/details/126444385