zoukankan      html  css  js  c++  java
  • C++并发编程 互斥和同步

    C++并发编程 异步任务(async)

    线程基本的互斥和同步工具类, 主要包括:
      std::mutex 类
      std::recursive_mutex 类
      std::timed_mutex 类
      std::recursive_timed_mutex 类
      std::lock_guard 类型模板
      std::unique_lock 类型模板
      std::lock 函数模板
      std::once_flag 类
      std::call_once 函数模板

     

    std::mutex 类

      std::mutex 上锁须要调用 lock() 或 try_lock(), 当有一个线程获取了锁, 其它线程想要取得此对象的锁时, 会被阻塞(lock)或失败(try_lock). 当线程完成共享数据的保护后, 需要调用 unlock 进行释放锁.
      std::mutex 不支嵌套, 如果两次调用 lock, 会产生未定义行为.

    std::recursive_mutex 类

    使用方法同 std::mutex, 但 std::recursive_mutex 支持一个线程获取同一个互斥量多次,而没有对其进行一次释放. 但是同一个线程内, lock 与 unlock 次数要相等, 否则其它线程将不能取得任何机会.
    其原理是, 调用 lock 时, 当调用线程已持有锁时, 计数加1; 调用 try_lock 时, 尝试取得锁, 失败时不会阻塞, 成功时计数加1; 调用 unlock 时, 计数减1, 如果是最后一个锁时, 释放锁.
    需要注意的是: 调用 try_lock时, 如果当前线程未取得锁, 即使没有别的线程取得锁, 也有可能失败.

    std::timed_mutex 类

    std::timed_mutex 在 std::mutex 的基础上支持让锁超时. 上锁时可以调用 try_lock_for, try_lock_until 设置超时值.
    try_lock_for 的参数是需要等待的时间, 当参数小于等于0时会立即返回, 效果和使用 try_lock 一样.
    try_lock_until 传入的参数不能小于当前时间, 否则会立即返回, 效果和使用 try_lock 一样. 实际上 try_lock_for 内部也是调用 try_lock_until 实现的.
    tm.try_lock_for(std::chrono::milliseconds(1000)) 与 tm.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1000)) 等价, 都是等待1s.

    std::recursive_timed_mutex 类

    std::recursive_timed_mutex 在 std::recursive_mutex 的基础上, 让锁支持超时.
    用法同 std::timed_mutex, 超时原理同 std::recursive_mutex.

    std::lock_guard 类型模板

    std::lock_guard 类型模板为基础锁包装所有权. 指定的互斥量在构造函数中上锁, 在析构函数中解锁.
    这就为互斥量锁部分代码提供了一个简单的方式: 当程序运行完成时, 阻塞解除, 互斥量解锁(无论是执行到最后, 还是通过控制流语句break或return, 亦或是抛出异常).
    std::lock_guard 不支持拷贝构造, 拷贝赋值和移动构造.

    std::unique_lock 类型模板

    std::unique_lock 类型模板比 std::loc_guard 提供了更通用的所有权包装器.
    std::unique_lock 可以调用 unlock 释放锁, 而后当再次需要对共享数据进行访问时再调用 lock(), 但是必须注意一次 lock 对应一次 unlock, 不能连续多次调用同一个 lock 或 unlock.
    std::unique_lock 不支持拷贝构造和拷贝赋值, 但是支持移动构造和移动赋值.
    std::unique_lock 比 std::loc_guard 还增加了另外几种构造方式:
    unique_lock(_Mutex& _Mtx, adopt_lock_t) 构建持有锁实例, 其不会调用 lock 或 try_lock, 但析构时默认会调用 unlock.
    unique_lock(_Mutex& _Mtx, defer_lock_t) 构建非持有锁实例, 其不会调用 lock 或 try_lock, 如果没有使用 std::lock 等函数修改标志, 析构时也不会调用 unlock.
    unique_lock(_Mutex& _Mtx, try_to_lock_t) 尝试从互斥量上获取锁, 通过调用 try_lock
    unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time) 在给定时间长度内尝试获取锁
    unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time) 在给定时间点内尝试获取锁
    bool owns_lock() const 检查是否拥有一个互斥量上的锁

    std::lock 函数模板

    std::lock 函数模板提供同时锁住多个互斥量的功能, 且不会有因改变锁的一致性而导致的死锁. 其声明如下:
    template<typename LockableType1,typename... LockableType2> void lock(LockableType1& m1,LockableType2& m2...);

        // 使用互斥量保护代码
        typedef std::lock_guard<std::mutex>         MutexLockGuard;
        typedef std::unique_lock<std::mutex>        UniqueLockGuard;
    
        class Func
        {
            int i;
            std::mutex& m;
        public:
            Func(int i_, std::mutex& m_) : i(i_), m(m_) {}
            void operator() ()
            {
                //MutexLockGuard lk(m);
                UniqueLockGuard lk(m);
                for (unsigned j = 0; j < 10; ++j)
                {
                    std::cout << i << " ";
                }
                std::cout << std::endl;
            }
        };
    
        std::mutex m;
        std::vector<std::thread> threads;
    
        for (int i = 1; i < 10; i++)
        {
            Func f(i, m);
            threads.push_back(std::thread(f));
        }
        std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 对每个线程调用join()   
        // 同时对多个 mutex 上锁
        std::mutex m1;
        std::mutex m2;
        //std::unique_lock<std::mutex> lock_a(m1, std::defer_lock);
        //std::unique_lock<std::mutex> lock_b(m2, std::defer_lock); // std::def_lock 留下未上锁的互斥量
        //std::lock(lock_a, lock_b); // 互斥量在这里上锁, 并修改对象的上锁标志
        std::lock(m1, m2); // 锁住两个互斥量
        std::lock_guard<std::mutex> lock_a(m1, std::adopt_lock); // std::adopt_lock 参数表示对象已经上锁,因此不会调用 lock 函数
        std::lock_guard<std::mutex> lock_b(m2, std::adopt_lock); 

    std::call_once 函数模板

    如果多个线程需要同时调用某个函数,std::call_once 可以保证多个线程对该函数只调用一次, 并且是线程安全的.

    线程安全的延迟初始化

    -- 使用 std::call_once 和 std::once_flag 

    考虑下面的代码, 每个线程必须等待互斥量,以便确定数据源已经初始化, 这导致了线程资源产生不必要的序列化问题.

            std::shared_ptr<some_resource> resource_ptr;
            std::mutex resource_mutex;
            void foo()
            {
                std::unique_lock<std::mutex> lk(resource_mutex); // 所有线程在此序列化
                if (!resource_ptr)
                {
                    resource_ptr.reset(new some_resource); // 只有初始化过程需要保护
                }
                lk.unlock();
                resource_ptr->do_something();
            }

    使用双重检查锁优化上述代码, 指针第一次读取数据不需要获取锁, 并且只有在指针为NULL时才需要获取锁; 然后, 当获取锁之后, 指针会被再次检查一遍(这就是双重检查的部分), 避免另一的线程在第一次检查后再做初始化, 并且让当前线程获取锁.
    这样同样存在问题, 即潜在的条件竞争, 因为外部的读取锁①时没有与内部的写入锁进行同步③, 因此就会产生条件竞争,这个条件竞争不仅覆盖指针本身, 还会影响到其指向的对象:
    即使一个线程知道另一个线程完成对指针进行写入, 它可能没有看到新创建的some_resource实例, 然后调用do_something()④后, 得到不正确的结果. 这在C++标准中被指定为“未定义行为”.

            void undefined_behaviour_with_double_checked_locking()
            {
                if (!resource_ptr) // 1
                {
                    std::lock_guard<std::mutex> lk(resource_mutex);
                    if (!resource_ptr) // 2
                    {
                        resource_ptr.reset(new some_resource); // 3
                    }
                }
                resource_ptr->do_something(); // 4
            }

      C++的解决方法:

            std::shared_ptr<some_resource> resource_ptr;
            std::once_flag resource_flag; // 1
            
            void init_resource()
            {
                resource_ptr.reset(new some_resource);
            }
            void foo()
            {
                std::call_once(resource_flag,init_resource); // 可以完整的进行一次初始化
                resource_ptr->do_something();
            }

      线程安全类成员的延迟初始化

            class X
            {
            private:
                connection_info connection_details;
                connection_handle connection;
                std::once_flag connection_init_flag;
                void open_connection()
                {
                    connection = connection_manager.open(connection_details);
                }
            public:
                X(connection_info const& connection_details_) : connection_details(connection_details_) {}
                void send_data(data_packet const& data) // 1
                {
                    std::call_once(connection_init_flag, &X::open_connection, this); // 2
                    connection.send_data(data);
                }
                data_packet receive_data() // 3
                {
                    std::call_once(connection_init_flag, &X::open_connection, this); // 2
                    return connection.receive_data();
                }
            };

     

    boost::shared_lock

    读者-写者锁 boost::shared_lock, 允许两中不同的使用方式:一个“作者”线程独占访问和共享访问, 让多个“读者”线程并发访问. (C++11标准不支持)
    其性能依赖与参与其中的处理器数量, 也与读者和写者线程的负载有关. 一种典型的应用:

        #include <map>
        #include <string>
        #include <mutex>
        #include <boost/thread/shared_mutex.hpp>
        class dns_entry;
        class dns_cache
        {
            std::map<std::string, dns_entry> entries;
            mutable boost::shared_mutex entry_mutex;
        public:
            dns_entry find_entry(std::string const& domain) const
            {
                boost::shared_lock<boost::shared_mutex> lk(entry_mutex); // 1
                std::map<std::string, dns_entry>::const_iterator const it =
                    entries.find(domain);
                return (it == entries.end()) ? dns_entry() : it->second;
            }
            void update_or_add_entry(std::string const& domain,
                dns_entry const& dns_details)
            {
                std::lock_guard<boost::shared_mutex> lk(entry_mutex); // 2
                entries[domain] = dns_details;
            }
        };
        

     

     

  • 相关阅读:
    中产阶级_百度百科
    第二天--设置一个数据模型
    请大家支持珊瑚虫~~请大家帮帮soff~~
    Linux Socket学习(十二)
    Debian下调整时间
    Emacs 牵手 scim
    Linux Socket学习(十三)
    mplayer解码
    创建第一个Symfony工程
    页面的基本创建
  • 原文地址:https://www.cnblogs.com/diysoul/p/5937075.html
Copyright © 2011-2022 走看看