• C++11多线程


    (1)std::thread

    在C++11之前,C++语言层面是不支持多线程的,想利用C++实现并发程序,借助操作系统的API实现跨平台的并发程序存在着诸多不便。在C++11中,终于提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类,编写跨平台的多线程代码就方便了许多。

    C++11提供的std::thread在开发多线程方面带来了便捷。

    #include <iostream>
    #include <thread>
    ​
    void threadfunc()
    {
            std::cout << "thread func" << std::endl;
    }
    ​
    ​
    int main()
    {
        std::thread t1(threadfunc);
        t1.join();   //等待threadfunc运行结束
     
        return 0;
    }
    

    首先定义线程对象t1,线程函数threadfunc运行在线程对象t1中,当线程创建成功并执行线程函数后,一定要保证线程函数运行结束才能退出,这里调用了join()函数阻塞线程,直到threadfunc()运行结束,回收对应创建线程的资源。如果不阻塞线程,就不能保证线程对象t1threadfunc()运行期间有效,下面不调用join()阻塞线程。

    #include <iostream>
    #include <thread>
    ​
    void threadfunc()
    {
        std::cout << "thread func" << std::endl;
    }
    ​
    ​
    int main()
    {
        std::thread t1(threadfunc);
        //t1.join();   //等待threadfunc运行结束
     
        return 0;
    }
    

    在运行时会引起程序崩溃。

    除了调用join()阻塞线程,保证线程对象在线程函数运行期间的有效性,还可以通过线程分离的手段实现,调用detach()函数使得线程对象与线程函数分离,这样,在线程函数运行期间,线程对象与线程函数就没有联系了,此时的线程是作为后台线程去执行,detach()后就无法再和线程发生联系,也不能通过join()来等待线程执行完毕,线程何时执行完无法控制,它的资源会被init进程回收,所以,通常不采用detach()方法。

    #include <iostream>
    #include <thread>
    ​
    void threadfunc()
    {
        std::cout << " detach thread func" << std::endl;
         
    }
    ​
    int main()
    {
        
        std::thread t1(threadfunc);
        t1.detach();      //线程分离
    ​
        return 0;
    }
    

    这里调用detach()实现线程分离,但是运行后,主线程退出的时候threadfunc()还没有输出“detach thread func”threadfunc()什么时候运行结束也无法确定,为了看到所创建的线程运行结果,在主线程等待一下再退出。

    #include <iostream>
    #include <thread>
    #include <chrono>   //时间
    ​
    void threadfunc()
    {
        std::cout << "detach thread func" << std::endl;
    }
    ​
    ​
    int main()
    {
        
        std::thread t1(threadfunc);
        t1.detach();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
            break;
        }
        
        return 0;
    }
    

    此时运行结果:

    detach thread func
    

    通过std::thread创建的线程是不可以复制的,但是可以移动。

    #include <iostream>
    #include <thread>
    #include <chrono>
    ​
    void threadfunc()
    {
       
        std::cout << "move thread func" << std::endl;
        
        
    }
    ​
    ​
    int main()
    {
        
        std::thread t1(threadfunc);
        std::thread t2(std::move(t1));
       
        t2.join();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
            break;
        }
        
        return 0;
    }
    

    输出结果:

    move thread func
    

    移动后t1就不代表任何线程了,t2对象代表着线程threadfunc()。另外,还可以通过std::bind来创建线程函数。  

    #include <iostream>
    #include <thread>
    #include <chrono>     //时间
    #include <functional>  //std::bind
    ​
    class A {
    public:
        void threadfunc()
        {
            std::cout << "bind thread func" << std::endl;
        }
    };
    ​
    ​
    int main()
    {
        A a;
        std::thread t1(std::bind(&A::threadfunc,&a));
        t1.join();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
            break;
        }
        
        return 0;
    }
    ​
    

    创建一个类A,然后再main函数中将类A中的成员函数绑定到线程对象t1上,运行结果:

    bind thread func
    

    每个线程都有自己的线程标识,也就是线程ID,当线程创建成功后,可以通过get_id()来获取线程的ID。  

    #include <iostream>
    #include <thread>
    #include <chrono>
    #include <functional>
    ​
    class A {
    public:
        void threadfunc()
        {
            std::cout << "bind thread func" << std::endl;
        }
    };
    ​
    ​
    int main()
    {
        A a;
        std::thread t1(std::bind(&A::threadfunc,&a));
        
        std::cout << "main thread ID is : " << std::this_thread::get_id() << std::endl;
        std::cout << "t1 thread ID is : " << t1.get_id() << std::endl;
        
        t1.join();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
            break;
        }
        
        return 0;
    }
    

    std::this_thread::get_id()获取的是当前线程的ID,t1.get_id()获取的是所创建的t1对象中运行的线程ID,对应的ID分别为:

    main thread ID is : 11932
    t1 thread ID is : 12076
    bind thread func
    

    虽然get_id()可以获取线程的ID,但是其返回类型是thread::id,通过std::cout可以输出线程ID,但是这样使用似乎不太方面,要是能转换为整型就好了。其实可以将得到的线程ID写入到ostreamstring流中,转换成string类型,再转换成整型。

    #include <iostream>
    #include <thread>
    #include <chrono>
    #include <functional>
    #include <sstream>
    ​
    class A {
    public:
        void threadfunc()
        {
            std::cout << "bind thread func" << std::endl;
        }
    };
    ​
    ​
    int main()
    {
        A a;
        std::thread t1(std::bind(&A::threadfunc, &a));
    ​
        std::ostringstream os1;
        os1 << t1.get_id() << std::endl;
        std::string strID = os1.str();            //转换成string类型
        int threadID = atoi(strID.c_str());       //转换成int类型
        std::cout << "t1 thread ID is : " << threadID << std::endl;
    ​
        t1.join();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
            break;
        }
    ​
        return 0;
    }
    

    输出结果:

    t1 thread ID is : 6956
    bind thread func
    

    2、std::mutex

    进入多线程编程的世界,除了要牢牢掌握std::thread使用方法,还要掌握互斥量(锁)的使用,这是一种线程同步机制,在C++11中提供了4中互斥量。

    std::mutex;                  //非递归的互斥量
    std::timed_mutex;            //带超时的非递归互斥量
    std::recursive_mutex;        //递归互斥量
    std::recursive_timed_mutex;  //带超时的递归互斥量

    从各种互斥量的名字可以看出其具有的特性,在实际开发中,常用就是std::mutex,它就像是一把锁,我们需要做的就是对它进行加锁与解锁。  

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <chrono>
    ​
    std::mutex g_mutex;
    ​
    void func()
    {
    ​
        std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
        
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        
        std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
    ​
    }
    int main()
    {
        std::thread t1(func);
        std::thread t2(func);
        std::thread t3(func);
        std::thread t4(func);
        std::thread t5(func);
    ​
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
    ​
        return 0;
    }
    

    创建了5个线程,然后分别调用func()函数,得到结果:

    entry func test thread ID is : entry func test thread ID is : 19180
    entry func test thread ID is : 3596
    13632
    entry func test thread ID is : 9520
    entry func test thread ID is : 4460
    leave func test thread ID is : 13632
    leave func test thread ID is : 19180
    leave func test thread ID is : leave func test thread ID is : 9520
    3596
    leave func test thread ID is : 4460
    

    可以看出,并没有按顺序去执行线程函数,后面创建的线程并没有等待前面的线程执行完毕,导致结果混乱,下面用std::mutex进行控制:

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <chrono>
    ​
    std::mutex g_mutex;
    ​
    void func()
    {
       g_mutex.lock();
    ​
        std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
        
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        
        std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
    ​
        g_mutex.unlock();
    }
    int main()
    {
        std::thread t1(func);
        std::thread t2(func);
        std::thread t3(func);
        std::thread t4(func);
        std::thread t5(func);
    ​
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
    ​
        return 0;
    }
    

    只要线程进入func()函数就进行加锁处理,当线程执行完毕后进行解锁,保证每个线程都能按顺序执行。

    虽然通过lock()unlock()可以解决线程之间的资源竞争问题,但是这里也存在不足。  

    func()
    {
        //加锁
        执行逻辑处理;    //如果该过程抛出异常导致程序退出了,就没法unlock
        //解锁
          
    }
    ​
    int main()
    {
        ......
    }
    

    func()中再执行逻辑处理中程序因为某些原因退出了,此时就无法unlock()了,这样其他线程也就无法获取std::mutex,造成死锁现象,其实在加锁之前可以通过trylock()尝试一下能不能加锁。实际开发中,通常也不会这样写代码,而是采用lock_guard来控制std::mutex

    template <class _Mutex>
    class lock_guard { 
    public:
        using mutex_type = _Mutex;
    ​
        explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) 
        { 
            _MyMutex.lock();     //构造函数加锁       
        }
    ​
        lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx)
        { 
        }
    ​
        ~lock_guard() noexcept
        { 
            _MyMutex.unlock();   //析构函数解锁
        }
    ​
        lock_guard(const lock_guard&) = delete;
        lock_guard& operator=(const lock_guard&) = delete;
    ​
    private:
        _Mutex& _MyMutex;
    };
    

    lock_guard是类模板,在其构造函数中自动给std::mutex加锁,在退出作用域的时候自动解锁,这样就可以保证std::mutex的正确操作,这也是RAII(获取资源便初始化)技术的体现。

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <chrono>
    ​
    std::mutex g_mutex;
    ​
    ​
    void func()
    {
        std::lock_guard<std::mutex> lock(g_mutex);   //加锁
    ​
        std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
        
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        
        std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
    ​
       //退出作用域后,lock_guard对象析构就自动解锁
    }
    int main()
    {
        std::thread t1(func);
        std::thread t2(func);
        std::thread t3(func);
        std::thread t4(func);
        std::thread t5(func);
    ​
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
    ​
        return 0;
    }
    

    运行结果:

    entry func test thread ID is : 19164
    leave func test thread ID is : 19164
    entry func test thread ID is : 15124
    leave func test thread ID is : 15124
    entry func test thread ID is : 2816
    leave func test thread ID is : 2816
    entry func test thread ID is : 17584
    leave func test thread ID is : 17584
    entry func test thread ID is : 15792
    leave func test thread ID is : 15792
    

    3、std::condition_variable

    条件变量是C++11提供的另外一种线程同步机制,通过判断条件是否满足,决定是否阻塞线程,当线程执行条件满足的时候就会唤醒阻塞的线程,常与std::mutex配合使用,C++11提供了两种条件变量。

    • std::condition_variable,配合std::unique_lock<std::mutex>使用,通过wait()函数阻塞线程;
    • std::condition_variable_any,可以和任意带有lock()unlock()语义的std::mutex搭配使用,比较灵活,但是其效率不及std::condition_variable
    std::unique_lock:C++11提供的 std::unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。std::unique_lockstd::lock_guard使用更加灵活,功能更加强大。使用std::unique_lock需要付出更多的时间、性能成本。

    下面利用std::mutexstd::condition_variable实现生产者与消费者模式。

    #include <iostream>
    #include <condition_variable>
    #include <thread>
    #include <list>
    #include <mutex>
    #include <chrono>
    ​
    class CTask {
    public:
        CTask(int taskID)
        {
            this->taskId = taskID;
        }
    ​
        void dotask()
        {
            std::cout << "consumer a task Id is " << taskId << std::endl;
        }
    private:
        int taskId;
    };
    ​
    ​
    std::list<std::shared_ptr<CTask>> g_task;
    std::mutex g_mutex;
    std::condition_variable g_conv;
    ​
    //生产者线程
    void ProdecerFunc()
    {
        
        int n_taskId = 0;
        std::shared_ptr<CTask> ptask = nullptr;
        while (true)
        {
            ptask = std::make_shared<CTask >(n_taskId); //创建任务
          
            {
                std::lock_guard<std::mutex> lock(g_mutex);
                g_task.push_back(ptask);
                std::cout << "produce a task Id is " << n_taskId << std::endl;
    ​
            }
            //唤醒线程
            g_conv.notify_one();
    ​
            n_taskId++;
    ​
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
    ​
    //消费者线程
    void ConsumerFunc()
    {
        std::shared_ptr<CTask> ptask = nullptr; 
        while (true)
        {
            std::unique_lock<std::mutex> lock(g_mutex);
            while (g_task.empty())  //即使被唤醒还要循环判断一次,防止虚假唤醒
            {
                g_conv.wait(lock);
            }
    ​
            ptask = g_task.front();  //取出任务
            g_task.pop_front();
    ​
            if (ptask == nullptr)
            {
                continue;
            }
            ptask->dotask();       //执行任务
            
        }
    }
    ​
    int main()
    {
        std::thread t1(ConsumerFunc);
        std::thread t2(ConsumerFunc);
        std::thread t3(ConsumerFunc);
    ​
        std::thread t4(ProdecerFunc);
    ​
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        
        return 0;
    }
    

    创建3个消费者线程,一个生产者线程,当存放任务的std::list为空时,消费者线程阻塞,当生产者线程生产一个任务放入std::list中时候,此时满足条件,条件变量就可以唤醒阻塞的线程去执行任务。

    produce a task Id is 0
    consumer a task Id is 0
    produce a task Id is 1
    consumer a task Id is 1
    produce a task Id is 2
    consumer a task Id is 2
    produce a task Id is 3
    consumer a task Id is 3
    produce a task Id is 4
    consumer a task Id is 4
    produce a task Id is 5
    consumer a task Id is 5
    produce a task Id is 6
    consumer a task Id is 6
    produce a task Id is 7
    consumer a task Id is 7
    ......
    

    条件变量的使用过程可以归纳如下:

    • 拥有条件变量的线消费者程获取互斥锁;
    • 消费者线程循环检查条件是否满足,不满足则阻塞等待,此时释放互斥锁;
    • 当生产者线程产生任务后,调用notify_one()或者notify_all()唤醒阻塞的消费者线程;
    • 当消费者线程被唤醒后再次获得互斥锁去执行任务;

     

    4、thread_local

    C++11中提供了thread_localthread_local定义的变量在每个线程都保存一份副本,而且互不干扰,在线程退出的时候自动销毁。

    #include <iostream>
    #include <thread>
    #include <chrono>
    ​
    thread_local int g_k = 0;
    ​
    void func1()
    {
        while (true)
        {
            ++g_k;
        }
    }
    ​
    void func2()
    {
        while (true)
        {
            std::cout << "func2 thread ID is : " << std::this_thread::get_id() << std::endl;
            std::cout << "func2 g_k = " << g_k << std::endl;
            
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    ​
    }
    ​
    int main()
    {
        std::thread t1(func1);
        std::thread t2(func2);
    ​
        t1.join();
        t2.join();
    ​
        return 0;
    }
    

    func1()g_k循环加1操作,在func2()每个1000毫秒输出一次g_k的值:

    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    func2 thread ID is : 15312
    func2 g_k = 0
    ​
    ......
    

    可以看出func2()中的g_k始终保持不变。

    参考:

    https://zhuanlan.zhihu.com/p/157171731

      

      

     

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

  • 相关阅读:
    [centos6.5]添加eclipse快捷方式
    Maven 实用命令和技巧
    MyEclipse 15 集成SVN
    Eclipse不给提示no default proposals
    Eclipse快捷键
    Maven打包排除不需要的文件。
    MySQL用法
    Idea反向生成JavaBean
    java.lang.NoClassDefFoundError: [Lorg/hibernate/engine/FilterDefinition
    Spring+SpringMVC+MyBatis+Maven 服务端XML配置
  • 原文地址:https://www.cnblogs.com/carsonzhu/p/13724488.html
Copyright © 2020-2023  润新知