学习C++并发编程笔记-互斥与条件变量


/*
 使用互斥量保护共享数据
 */
/*
 C++17
 */
//中添加了一个新特性,称为模板类参数推导,这样类似 std::locak_guard 这样简单的 模板类型的模板参数列表可以省略
//std::lock_guard guard(some_mutex);
//C++17中的一种加 强版数据保护机制—— std::scoped_lock
// std::scoped_lock<> 一种新的RAII类型模板类型, 与 std::lock_guard<> 的功能等价,这个新类型能接受不定数量的互斥量类型作为模板参数, 以及相应的互斥量(数量和类型)作为构造参数。互斥量支持构造即上锁,与 std::lock 的用法 相同,其解锁阶段是在析构中进行。
std::mutex a_m;
std::mutex b_m;
void testScopeLock() {
//    std::lock(a_m, b_m);
//    std::lock_guard lg(a_m, std::adopt_lock);
//    std::lock_guard lg(b_m, std::adopt_lock);
//
    //等价于上面的操作
    std::scoped_lock sl(a_m, b_m);
}

//一个互斥量可以在同一线程上多次上锁,标准库 中 std::recursive_mutex 提供这样的功能

//1. std::unique_lock多了一个参数值 ,将 std::defer_lock 作为第二个参数传递进 去,表明互斥量应保持解锁状态。这样,就可以被 std::unique_lock 对象(不是互斥量)的 lock()函数所获取,或传递 std::unique_lock 对象到 std::lock() 中
//2. std::unique_lock 的灵活性同样也允许实例在销毁之前放弃其拥有的锁。可以使用unlock()来 做这件事,如同一个互斥量: std::unique_lock 的成员函数提供类似于锁定和解锁互斥量的 功能。 std::unique_lock 实例在销毁前释放锁的能力,当锁没有必要在持有的时候,可以在 特定的代码分支对其进行选择性的释放
void testUniqueLock() {
    //延迟加锁
    std::unique_lock<std::mutex> lock_a(a_m,std::defer_lock); //
    std::unique_lock<std::mutex> lock_b(b_m,std::defer_lock); // 1  std::defer_lock 留下未上锁的互斥量
    std::lock(lock_a,lock_b); // 2 互斥量在这里上锁
    //等价lock_a.lock(); lock_b.lock();
    
    //提前解锁
    lock_a.unlock();
    lock_b.unlock();
    
    
    std::unique_lock<std::mutex> my_lock(a_m);
    //do something
    my_lock.unlock(); // 1 不要让锁住的互斥量越过process()函数的调用 result_type
    //do something
    my_lock.lock(); // 2 为了写入数据,对互斥量再次上锁 write_result(data_to_process,result);
}
/*
 lock_guard和unique_lock区别
 lock_guard 和 unique_lock 都是 C++ 标准库提供的互斥锁 RAII 封装工具,用于实现互斥访问,但它们有一些不同之处:

 lock_guard 是基于互斥锁 std::mutex 实现的,unique_lock 是基于通用锁 std::unique_lock 实现的,unique_lock 可以实现比 lock_guard 更灵活的锁操作。
 lock_guard 是不可移动的(moveable),即不能拷贝、赋值、移动,只能通过构造函数初始化和析构函数销毁,unique_lock 是可移动的,可以拷贝、赋值、移动。
 unique_lock 提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等。
 unique_lock 比 lock_guard 更重,因为它有更多的功能,更多的开销。如果只需要简单的互斥保护,使用 lock_guard 更好。

 另外,unique_lock 支持手动解锁,而 lock_guard 不支持。
 */

//3.std::unique_lock 还可以进行移动拷贝

/*
 2. call_once
 */
int a = 0;


void testCallOnce() {
    static std::once_flag o_f;
    std::call_once(o_f, []() {
        a += 1;
    });
    std::cout << "a = " << a << std::endl;
}
/*
 3.读写锁
 */
//C++17标准库提供了两种非常好的互斥量 —— std::shared_mutex 和 std::shared_timed_mutex 。C++14只提供 了 std::shared_timed_mutex
//并且在C++11中并未提供任何互斥量类型。如果你还在用支持 C++14标准之前的编译器,那你可以使用Boost库中实现的互斥 量。 std::shared_mutex 和 std::shared_timed_mutex 的不同点在 于, std::shared_timed_mutex 支持更多的操作方式(参考4.3节), std::shared_mutex 有更高的 性能优势,从而不支持更多的操作。


class dns_entry {
    
};
class dns_cache {
    std::map<std::string,dns_entry> entries;
    mutable std::shared_mutex entry_mutex;
public:
    dns_entry find_entry(std::string const& domain) const { std::shared_lock<std::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<std::shared_mutex> lk(entry_mutex); // 2
        entries[domain]=dns_details;
        
    }
};
/*
 条件变量
 */
//我们不仅想要保护数据,还想对单 独的线程进行同步。例如,在第一个线程完成前,可能需要等待另一个线程执行完成。通常 情况下,线程会等待一个特定事件发生,或者等待某一条件达成。这可能需要定期检查“任务 完成”标识,或将类似的东西放到共享数据中,但这与理想情况差很多。像这种情况就需要在 线程中进行同步
//C++标准库提供了一些工具可用于同步操作,形式上表现为条件变量 (condition variables)和期望值(futures)。并发技术规范(TS)中,为期望值添加了更多的操作, 并与新的同步工具锁存器(latches)(轻量级锁资源)和栅栏机制(barriers)一起使用。
//C++标准库对条件变量有两套实 现: std::condition_variable 和 std::condition_variable_any
//这两个实现都包含 在  头文件的声明中。
//两者都需要与一个互斥量一起才能工作(互斥量是 为了同步);前者仅限于与 std::mutex 一起工作,而后者可以和任何满足最低标准的互斥量一 起工作,从而加上了_any的后缀。
//注意要用unique_lock,它可以随时解锁再次上锁
std::condition_variable cv;
std::vector<int> vec;
std::mutex cv_m;
void handle_data () {
    while (true) {
        std::unique_lock ul(cv_m);
        cv.wait(ul, []{
            return !vec.empty();
        });
        int num = vec.back();
        std::cout << "num2 = " << num << std::endl;
        vec.pop_back();
    }
}

void add_data(int i) {
    while (i) {
        std::cout << "add_data " << std::endl;
        std::unique_lock ul(cv_m);
        vec.push_back(i);
        --i;
        cv.notify_one();
    }
    std::cout << "add_data " << std::endl;
}

/*
 期望
 */
//C++标准库中,有两种期望值,使用两种类型模板实现,声明在  头文件中: 唯一期望 值(unique futures)( std::future<> )和共享期望值(shared futures)( std::shared_future<> )。
// std::future 的实例只能与一个指定事件相关 联,而 std::shared_future 的实例就能关联多个事件。
//。后者的实现中,所有实例会在同时变 为就绪状态,并且他们可以访问与事件相关的任何数据
//用于获取后台返回值
//与 std::thread 做的方式一样, std::async 允许你通过添加额外的调用参数,向函数传递额 外的参数。当第一个参数是一个指向成员函数的指针,第二个参数提供有这个函数成员类的 具体对象(不是直接的,就是通过指针,还可以包装在 std::ref 中),剩余的参数可作为成员 函数的参数传入。否则,第二个和随后的参数将作为函数的参数,或作为指定可调用对象的 第一个参数。就如 std::thread ,当参数为右值时,拷贝操作将使用移动的方式转移原始数 据

int doSomething() {
    for (int i = 0; i < 10; ++i) {
        std::cout << "doSomething"<< std::endl;
    }
    return 1;
}
void testFuture() {
    std::future<int> f = std::async(doSomething);
    for (int i = 0; i < 10; ++i) {
        std::cout << "doSomething too"<< std::endl;
    }
    std::cout << "something done, return : " <<f.get()<<std::endl;
    /*
     f.get = doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     doSomething1
     2
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     go on doSomething1
     */
}


//std::promise 提供设定值的方式(类型为T),这个类型会和后面看到的 std::future 对 象相关联。一对 std::promise/std::future 会为这种方式提供一个可行的机制;期望值可以阻 塞等待线程,同时,提供数据的线程可以使用组合中的承诺值来对相关值进行设置,并将期 望值的状态置为“就绪”。

//可以通过一个给定的 std::promise 的get_future()成员函数来获取与之相关的 std::future 对 象,跟 std::packaged_task 的用法类似。当承诺值已经设置完毕(使用set_value()成员函数), 对应期望值的状态变为“就绪”,并且可用于检索已存储的值。当在设置值之前销 毁 std::promise ,将会存储一个异常。

std::promise<int> p;
int doSomething1() {
    for (int i = 0; i < 10; ++i) {
        std::cout << "doSomething1"<< std::endl;
    }
    p.set_value(2);
    for (int i = 0; i < 10; ++i) {
        std::cout << "go on doSomething1 "<< std::endl;
    }
    return 1;
}
void testPromise() {
    std::future<int> f = p.get_future();
    std::thread t1(doSomething1);
    std::cout << "f.get = " << f.get() << std::endl;
    t1.join();
}



void day2Test() {
//    std::thread t1(handle_data);
//    std::thread t2(add_data, 5);
//    t1.join();
//    t2.join();
    testPromise();
    
}

你可能感兴趣的:(学习c++并发编程笔记,学习,c++,笔记)