{"id":4750,"date":"2016-05-07T19:27:40","date_gmt":"2016-05-07T19:27:40","guid":{"rendered":"https:\/\/www.modernescpp.com\/index.php\/reader-writer-locks\/"},"modified":"2023-06-26T12:57:50","modified_gmt":"2023-06-26T12:57:50","slug":"reader-writer-locks","status":"publish","type":"post","link":"https:\/\/www.modernescpp.com\/index.php\/reader-writer-locks\/","title":{"rendered":"Reader-Writer Locks"},"content":{"rendered":"<p>With C++14 came reader-writer locks. The idea is straightforward and promising. Arbitrary reading threads can access the critical region simultaneously, but only one thread is allowed to write.<\/p>\n<p><!--more--><\/p>\n<h2>Minimized bottleneck<\/h2>\n<p>Reader-writer locks do not solve the fundamental problem &#8211; threads competing for access to a critical region. But reader-writer locks help a lot &#8211; to minimize the bottleneck. Let&#8217;s give an example.<\/p>\n<div style=\"background: #ffffff; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0; line-height: 125%;\"> 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49\r\n50\r\n51\r\n52\r\n53\r\n54\r\n55\r\n56\r\n57\r\n58\r\n59\r\n60\r\n61<\/pre>\n<\/td>\n<td>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #008000;\">\/\/ readerWriterLock.cpp<\/span>\r\n\r\n<span style=\"color: #0000ff;\">#include &lt;iostream&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;map&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;shared_mutex&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;string&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;thread&gt;<\/span>\r\n\r\nstd::map&lt;std::string,<span style=\"color: #2b91af;\">int<\/span>&gt; teleBook{{<span style=\"color: #a31515;\">\"Dijkstra\"<\/span>,1972},{<span style=\"color: #a31515;\">\"Scott\"<\/span>,1976},{<span style=\"color: #a31515;\">\"Ritchie\"<\/span>,1983}};\r\n\r\nstd::shared_timed_mutex teleBookMutex;\r\n\r\n<span style=\"color: #2b91af;\">void<\/span> addToTeleBook(<span style=\"color: #0000ff;\">const<\/span> std::string&amp; na, <span style=\"color: #2b91af;\">int<\/span> tele){\r\n  std::lock_guard&lt;std::shared_timed_mutex&gt; writerLock(teleBookMutex);\r\n  std::cout &lt;&lt; <span style=\"color: #a31515;\">\"\\nSTARTING UPDATE \"<\/span> &lt;&lt; na;\r\n  std::this_thread::sleep_for(std::chrono::milliseconds(500));\r\n  teleBook[na]= tele;\r\n  std::cout &lt;&lt; <span style=\"color: #a31515;\">\" ... ENDING UPDATE \"<\/span> &lt;&lt; na &lt;&lt; std::endl;\r\n}\r\n\r\n<span style=\"color: #2b91af;\">void<\/span> printNumber(<span style=\"color: #0000ff;\">const<\/span> std::string&amp; na){\r\n  std::shared_lock&lt;std::shared_timed_mutex&gt; readerLock(teleBookMutex);\r\n  std::cout &lt;&lt; na &lt;&lt; <span style=\"color: #a31515;\">\": \"<\/span> &lt;&lt; teleBook[na];\r\n}\r\n\r\n<span style=\"color: #2b91af;\">int<\/span> main(){\r\n\r\n  std::cout &lt;&lt; std::endl;\r\n\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader1([]{ printNumber(<span style=\"color: #a31515;\">\"Scott\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader2([]{ printNumber(<span style=\"color: #a31515;\">\"Ritchie\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> w1([]{ addToTeleBook(<span style=\"color: #a31515;\">\"Scott\"<\/span>,1968); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader3([]{ printNumber(<span style=\"color: #a31515;\">\"Dijkstra\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader4([]{ printNumber(<span style=\"color: #a31515;\">\"Scott\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> w2([]{ addToTeleBook(<span style=\"color: #a31515;\">\"Bjarne\"<\/span>,1965); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader5([]{ printNumber(<span style=\"color: #a31515;\">\"Scott\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader6([]{ printNumber(<span style=\"color: #a31515;\">\"Ritchie\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader7([]{ printNumber(<span style=\"color: #a31515;\">\"Scott\"<\/span>); });\r\n  std::<span style=\"color: #0000ff;\">thread<\/span> reader8([]{ printNumber(<span style=\"color: #a31515;\">\"Bjarne\"<\/span>); });\r\n\r\n  reader1.join();\r\n  reader2.join();\r\n  reader3.join();\r\n  reader4.join();\r\n  reader5.join();\r\n  reader6.join();\r\n  reader7.join();\r\n  reader8.join();\r\n  w1.join();\r\n  w2.join();\r\n\r\n  std::cout &lt;&lt; std::endl;\r\n\r\n  std::cout &lt;&lt; <span style=\"color: #a31515;\">\"\\nThe new telephone book\"<\/span> &lt;&lt; std::endl;\r\n  <span style=\"color: #0000ff;\">for<\/span> (<span style=\"color: #0000ff;\">auto<\/span> teleIt: teleBook){\r\n    std::cout &lt;&lt; teleIt.first &lt;&lt; <span style=\"color: #a31515;\">\": \"<\/span> &lt;&lt; teleIt.second &lt;&lt; std::endl;\r\n  }\r\n\r\n  std::cout &lt;&lt; std::endl;\r\n\r\n}\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>&nbsp;<\/p>\n<p>The <span style=\"font-family: courier new,courier;\">telebook<\/span> in line 9 is the shared variable, which has to be protected.<span style=\"font-family: courier new,courier;\"><\/span> Eight threads want to read the telephone book; two threads want to modify it (lines 30 &#8211; 39). To access the telephone book simultaneously,&nbsp;the reading threads use the <span style=\"font-family: courier new,courier;\">std::shared_lock&lt;std::shared_timed_mutex&gt;&gt;<\/span> in line 22. This opposes the writing threads, which need exclusive access to the critical section. The exclusivity is given by the <span style=\"font-family: courier new,courier;\">std::lock_guard&lt;std::shared_timed_mutex&gt;&gt;<\/span> in line 14. In the end, the program displays (lines 54 &#8211; 57)&nbsp;the updated telephone book.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-4749\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/05\/readerWriterLocks.png\" alt=\"readerWriterLocks\" width=\"621\" height=\"324\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/05\/readerWriterLocks.png 621w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/05\/readerWriterLocks-300x157.png 300w\" sizes=\"auto, (max-width: 621px) 100vw, 621px\" \/><\/p>\n<p>The screenshot shows that the output of the reading threads overlaps while the writing thread is executed one after the other. It means that the reading operations are performed at the same time.<\/p>\n<p>That was easy. Too easy.<\/p>\n<\/p>\n<h3>Undefined behavior<\/h3>\n<p>The program has undefined behavior. What? Before you continue, stop for a few seconds and think. By the way, the concurrent access of <span style=\"font-family: courier new,courier;\">std::cout<\/span> is not the issue.<\/p>\n<p>The characteristic of a race condition is that at least two threads access the shared variable simultaneously, and at least one of them is a writer. Precisely what is happening in the program. A characteristic of the ordered associative container is that the reading of the container can modify it. It happens if the element is unavailable in the container &#8211; the case of &#8220;Bjarne&#8221;. If &#8220;Bjarne&#8221; is not found in the telephone book, a pair (&#8220;Bjarne&#8221;, 0) will be created from the read operation. For the details, have a look at <a href=\"http:\/\/en.cppreference.com\/w\/cpp\/container\/map\">cppreference.com<\/a>.<\/p>\n<h2>What&#8217;s next?<\/h2>\n<p>In the <a href=\"https:\/\/www.modernescpp.com\/index.php\/thread-safe-initialization-of-data\">next post<\/a>, I will continue with thread-safe data initialization in multithreading programs.<\/p>\n<p style=\"color: #000000;\">&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With C++14 came reader-writer locks. The idea is straightforward and promising. Arbitrary reading threads can access the critical region simultaneously, but only one thread is allowed to write.<\/p>\n","protected":false},"author":21,"featured_media":4749,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[366],"tags":[430],"class_list":["post-4750","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-multithreading","tag-lock"],"_links":{"self":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/4750","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/comments?post=4750"}],"version-history":[{"count":1,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/4750\/revisions"}],"predecessor-version":[{"id":6982,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/4750\/revisions\/6982"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media\/4749"}],"wp:attachment":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media?parent=4750"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/categories?post=4750"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/tags?post=4750"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}