C++线程之读写锁

随着C++ 14的发布,出现了读写锁。
这个用法很简单,应用也比较广泛。
要做到的是:任意读线程可以同时访问关键区域,但是只允许一个线程写入。


虽然读写器锁不能解决根本问题——线程竞争访问关键区域。但是读写器锁有很多用处—至少可以优化读操作的进行。
让我们举个例子:

// readerWriterLock.cpp

#include <iostream>
#include <map>
#include <shared_mutex>
#include <string>
#include <thread>

std::map<std::string,int> teleBook{{"Dijkstra",1972},{"Scott",1976},{"Ritchie",1983}};

std::shared_timed_mutex teleBookMutex;

void addToTeleBook(const std::string& na, int tele){
  std::lock_guard<std::shared_timed_mutex> writerLock(teleBookMutex);
  std::cout << "\nSTARTING UPDATE " << na;
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  teleBook[na]= tele;
  std::cout << " ... ENDING UPDATE " << na << std::endl;
}

void printNumber(const std::string& na){
  std::shared_lock<std::shared_timed_mutex> readerLock(teleBookMutex);
  std::cout << na << ": " << teleBook[na];
}

int main(){

  std::cout << std::endl;

  std::thread reader1([]{ printNumber("Scott"); });
  std::thread reader2([]{ printNumber("Ritchie"); });
  std::thread w1([]{ addToTeleBook("Scott",1968); });
  std::thread reader3([]{ printNumber("Dijkstra"); });
  std::thread reader4([]{ printNumber("Scott"); });
  std::thread w2([]{ addToTeleBook("Bjarne",1965); });
  std::thread reader5([]{ printNumber("Scott"); });
  std::thread reader6([]{ printNumber("Ritchie"); });
  std::thread reader7([]{ printNumber("Scott"); });
  std::thread reader8([]{ printNumber("Bjarne"); });

  reader1.join();
  reader2.join();
  reader3.join();
  reader4.join();
  reader5.join();
  reader6.join();
  reader7.join();
  reader8.join();
  w1.join();
  w2.join();

  std::cout << std::endl;

  std::cout << "\nThe new telephone book" << std::endl;
  for (auto teleIt: teleBook){
    std::cout << teleIt.first << ": " << teleIt.second << std::endl;
  }

  std::cout << std::endl;

}

第9行中的teleBook是共享变量,它必须被保护。
8个线程要读teleBook,2个线程要修改(行30—39)它。
要同时访问teleBook,读线程使用std::shared_lock<std::shared_timed_mutex>>第22行,这与写入线程相反,写入线程需要独占访问临界区。
而排他性由第14行中的std::lock_guard<std::shared_timed_mutex>>给出。最后,程序显示(第54-57行)更新后的teleBook。
这里写图片描述
屏幕截图显示,读取线程的输出是重叠的,而写入线程则是一个接一个地执行的。这意味着读取操作是同时进行的。


未定义的的行为

上面的程序有未定义的行为。啥?停几秒钟思考一下。
顺便说一下,std::cout的并发访问不是问题,是线程安全的。
竞争条件的特征是,至少两个线程同时访问共享变量,其中至少一个是写入器。
确切地说在程序中发生了什么?
有序关联容器的一个特征是容器的读取可以修改它。如果元素在容器中不可用,则会发生这种情况:
例如“Bjarne”,如果在teleBook中找不到“Bjarne”,则map容器会从读取操作中创建一对(“Bjarne”,0)。更多请看std::map的操作特点。

https://en.cppreference.com/w/cpp/container/map

原文地址:

http://www.modernescpp.com/index.php/reader-writer-locks

猜你喜欢

转载自blog.csdn.net/y396397735/article/details/81073280