zoukankan      html  css  js  c++  java
  • c++11 多线程 -- 基本使用

    c++11 多线程 – 基本使用

    前言:这篇文章仅针对没有使用过c++11线程库的童鞋来高速入门,也是自己的一个简单记录,内容比較基础。

    • 1.线程的基本使用
    • 2.相互排斥量
    • 3.条件变量
    • 4.原子变量
    • 补充

    1.线程的基本使用

    代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <thread>
    #include <iostream>
    
    int k = 0;
    
    void fun(void)
    {
        //线程休眠,chrono是c++11的时间相关库。
              std::this_thread::sleep_for(std::chrono::seconds(3));
        for(int i = 0; i < 10; ++i)
        {
            std::cout << "hello world" << std::endl;
            k++;
        }
    }
    
    int main(int argc, char *argv[])
    {
        //创建线程对象
        std::thread t1(fun);
        //输出线程id和cpu核数
        std::cout << "ID:" << t1.get_id() << std::endl;
        std::cout << "CPU:" << std::thread::hardware_concurrency() << std::endl;
        //主函数堵塞等待线程结束
        t1.join();
        //主函数和线程函数分离运行,线程变为后台线程
        //t1.detach();
    
        std::cout << k << std::endl;
    
        return EXIT_SUCCESS;
    }

    注意:
    1.linux下用gcc或clang必须加-pthread连接到线程库。否则会出错。
    2.主线程函数不能提前结束于新创建的线程函数。由于在c++11中。线程也是对象,主函数结束线程对象即销毁。
    3.t.join()是主函数堵塞等待线程结束才干结束,主函数会继续运行,并堵塞在return处
    t.detach()主函数和线程函数分离,各自运行各自的,线程变为后台线程。


    4.可通过bind和lambda创建线程
    能够将线程保存在容器中,以保证线程对象的声明周期。
    可是注意线程没有拷贝构造函数。有移动构造函数。
    这里写图片描写叙述
    图上能够看出拷贝构造函数为delete。


    2.相互排斥量

    分为4种
    std::mutex 独占的相互排斥量,不能递归使用
    std::timed_mutex 带超时的独占的相互排斥量。不能递归使用
    std::recursive_mutex 递归相互排斥量。不带超时功能
    std::recursive_timed_mutex 带超时的递归相互排斥量

    代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <iostream>
    #include <thread>
    #include <mutex>
    
    std::mutex g_lock;
    int i = 0;
    
    void func(void)
    {
        //使用RAII手法,在离开作用域时自己主动释放
        std::lock_guard<std::mutex>locker(g_lock);
        //正常的相互排斥锁上锁
        //g_lock.lock();
        i++;
        std::cout << i << std::endl;
        //相互排斥锁解锁
        //g_lock.unlock();
    }
    
    int main(int argc, char *argv[])
    {
        std::thread t1(func);
        std::thread t2(func);
        std::thread t3(func);
        t1.join();
        t2.join();
        t3.join();
    
        return EXIT_SUCCESS;
    }

    注意:
    1.多次获取相互排斥量可能会发生死锁,所以我们调用std::recursive_mutex递归锁,同意同一线程多次获得该锁,一般不要使用递归锁。原因:<1.用到递归锁会使得程序的逻辑变复杂,使用到递归锁的程序一般能够简化。<2.递归锁比非递归锁效率低。<3.递归锁的可重入次数是有限的。超过也会报错。
    2.能够使用带超时时间的相互排斥锁,避免堵塞在等待相互排斥锁上。
    3.unique_lock: 是一个通用的相互排斥量封装类。

    与lock_guard不同。它还支持延迟加锁、时间锁、递归锁、锁全部权的转移而且还支持使用条件变量。

    这也是一个不可复制的类,但它是能够移动的类。


    3.条件变量

    堵塞一个或多个线程。直到收到另外一个线程发来的通知或者超时,才会唤醒当前堵塞的进程
    条件变量须要和相互排斥量配合使用
    c++11提供了两种条件变量
    1.std::condition_variable。配合std::unique_lock进行wait操作
    2.std::condition_variable_any。和随意带有lock。unlock的mutex进行搭配使用,比較灵活但效率略低。
    条件变量的wait另一个重载的方法,能够设置一个条件,条件变量会先检查推断式是否满足条件。

    原理:
    当 std::condition_variable 对象的某个 wait 函数被调用的时候。它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被堵塞,直到另外一个线程在同样的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

    代码:用c++11多线程实现同步队列

    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <mutex>
    #include <thread>
    #include <condition_variable>
    #include <iostream>
    #include <list>
    #include <vector>
    #include <memory>
    #include <unistd.h>
    
    template<typename T>
    class SynQueue
    {
        public:
            //构造函数
            SynQueue(int MaxSize):
                m_maxsize(MaxSize) { }
    
            //将T类型对象放入队列
            void Put(const T&x)
            {
                std::lock_guard<std::mutex>locker(m_mutex);
                while(isFull())
                {
                    //假设满了。等待
                    m_notFull.wait(m_mutex);
                }
                m_queue.push_back(x);
                //通过条件变量唤醒一个线程。也能够全部线程
                m_notEmpty.notify_one();
            }
    
            //将T类型对象从队列取出
            void Take(T&x)
            {
                std::lock_guard<std::mutex> locker(m_mutex);
                while(isEmpty())
                {
                    std::cout << "no resource... please wait" << std::endl;
                    m_notEmpty.wait(m_mutex);
                }
                x = m_queue.front();
                m_queue.pop_front();
                m_notFull.notify_one();
            }
    
            //推断队列是否为空
            bool Empty()
            {
                std::lock_guard<std::mutex> locker(m_mutex);
                return m_queue.empty();
            }
    
            //推断队列是否为满
            bool Full()
            {
                std::lock_guard<std::mutex> locker(m_mutex);
                return m_queue.size() == m_maxsize;
            }
    
            //返回队列大小
            size_t Size()
            {
                std::lock_guard<std::mutex> locker(m_mutex);
                return m_queue.size();
            }
    
        private:
            //推断空或满,内部使用不须要加锁
            bool isFull() const
            {
                return m_queue.size() == m_maxsize;
            }
            bool isEmpty() const
            {
                return m_queue.empty();
            }
    
        private:
            //队列
            std::list<T>m_queue;
            //相互排斥锁
            std::mutex m_mutex;
            //不为空时的条件变量
            std::condition_variable_any m_notEmpty;
            //不为满时的条件变量
            std::condition_variable_any m_notFull;
            //队列最大长度
            int m_maxsize;
    };
    
    void func(SynQueue<int> *sq)
    {
        int ret;
        sq->Take(ret);
        std::cout << ret << std::endl;
    }
    
    int main(int argc, char *argv[])
    {
        //创建线程队列。长度最大为20
        SynQueue<int>syn(20);
        //放置数据对象
        for(int i = 0; i < 10; i++)
        {
            syn.Put(i);
        }
        std::cout << syn.Size() << std::endl;
    
        //线程不能拷贝,用容器和智能指针来管理线程生存
        std::vector<std::shared_ptr<std::thread>> tvec;
        //多循环一次,资源不足,堵塞最后一个线程,在后面加入一个资源。看该线程是否会被唤醒运行。
        for(int i = 0; i < 11; i++)
        {
            //创建线程而且将管理线程的智能指针保存到容器中
            tvec.push_back(std::make_shared<std::thread>(func, &syn));
            //变为后台线程
            tvec[i]->detach();
        }
        sleep(10);
        //加入一个资源
        syn.Put(11);
        sleep(10);
    
        return EXIT_SUCCESS;
    }

    运行结果:
    这里写图片描写叙述


    4.原子变量

    原子变量,为原子操作,不须要加锁
    std::atomic<T>
    详情可參考,这里仅简单举例使用方法
    cppreference atomic

    代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <atomic>
    #include <thread>
    #include <vector>
    #include <iostream>
    
    //创建int类型的原子变量
    std::atomic<int>atc(0);
    
    void func()
    {
        std::cout << atc << std::endl;
        原子变量自增
        atc++;
    }
    
    int main(int argc, char *argv[])
    {
        std::vector<std::thread>tvec;
        for(int i = 0; i < 10; i++)
        {
            std::thread t(func);
            //线程对象移动语义
            tvec.push_back(std::move(t));
            tvec[i].join();
        }
    
        return EXIT_SUCCESS;
    }

    补充:

    1.线程创建函数能够为各种可调用类型,函数指针,仿函数,lambda表达式。

    可是注意,线程參数会以默认的方式被拷贝到内部存储空间中。即便函数中的对应參数期待引用。线程运行函数内部能够訪问它们。
    可能我们想传递引用在线程运行函数内部改动并返回结果,但复制会导致达不到预期的效果,解决方式是传递引用參数时用std::ref来包装或者传递指针。


    2.线程初始化运行函数也能够传递一个成员函数
    std::thread tid(&x::func, &my_func)


    3.线程不支持拷贝,但支持全部权移动。


    4.假设线程运行函数使用了暂时变量可能会出现故障。线程调用了detach在后台运行时,暂时变量可能已经销毁,那么线程会訪问已经被销毁的变量。join能保证。

  • 相关阅读:
    上下,流动
    面对离去了的亲人,
    计算 star 之间 距离,
    咀嚼,
    python中的内嵌函数
    python中全局变量和局部变量
    python中函数的闭包
    python中函数的收集参数
    python中如何将局部变量扩展为全局变量(global关键字)
    python中的内嵌函数
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7048025.html
Copyright © 2011-2022 走看看