diff --git a/_posts/2019-11-25-totw-161.md b/_posts/2019-11-25-totw-161.md index fc958d3..3ac1967 100644 --- a/_posts/2019-11-25-totw-161.md +++ b/_posts/2019-11-25-totw-161.md @@ -71,7 +71,7 @@ return SomeExpression(args); #### Inline Expressions Under Test Into GoogleTest's EXPECT_THAT
-auto actual = SortedAges(args);
+std::vector<string> actual = SortedAges(args);
 EXPECT_THAT(actual, ElementsAre(21, 42, 63));
 
@@ -88,6 +88,9 @@ makes it clear at a glance what's being tested, and by avoiding giving a name to `actual` ensures that it cannot be unintentionally re-used. It also allows the testing framework to show the failing call in error output. +Note: the shorter version hides the expected type of `SortedAges`. If verifying +the type is important, consider declaring a variable in order to show its type. + #### Use Matchers to Eliminate Variables in Tests. [Matchers](https://github.com/google/googletest/blob/master/docs/reference/matchers.md) @@ -129,17 +132,13 @@ three fields of the same proto message. Using a local variable to alias the relevant message can clean it up:
-auto& subsubmessage = *myproto.mutable_submessage()->mutable_subsubmessage();
+SubSubMessage& subsubmessage =
+    *myproto.mutable_submessage()->mutable_subsubmessage();
 subsubmessage.set_foo(21);
 subsubmessage.set_bar(42);
 subsubmessage.set_baz(63);
 
-Note: Specify the type of the reference explicitly when it helps readability, -and use `auto` only to ["avoid type names that are noisy, obvious, or -unimportant - cases where the type doesn't aid in clarity for the -reader"](https://google.github.io/styleguide/cppguide.html#auto). - In some cases this can also help the compiler to generate better code as it doesn't need to prove that the repeated expression returns the same value each time. Beware of premature optimization, though: if eliminating a common @@ -163,8 +162,8 @@ in C++11 we could write
 for (const auto& name_and_age : ages_by_name) {
-  const auto& name = name_and_age.first;
-  const auto& age = name_and_age.second;
+  const std::string& name = name_and_age.first;
+  const int& age = name_and_age.second;
 
   if (IsDisallowedName(name)) continue;
   if (age < 18) children.insert(name);
diff --git a/_posts/2019-12-12-totw-146.md b/_posts/2019-12-12-totw-146.md
index 6e6d8af..e686b68 100644
--- a/_posts/2019-12-12-totw-146.md
+++ b/_posts/2019-12-12-totw-146.md
@@ -87,7 +87,7 @@ to *provide* the definition via `=default`. For example:
 
 
 struct Foo {
-  Foo() = default; // "Used-declared", NOT "user-provided".
+  Foo() = default; // "User-declared", NOT "user-provided".
 
   int v;
 };
diff --git a/_posts/2020-09-01-totw-140.md b/_posts/2020-09-01-totw-140.md
index 5a4e962..47785fb 100644
--- a/_posts/2020-09-01-totw-140.md
+++ b/_posts/2020-09-01-totw-140.md
@@ -87,7 +87,8 @@ All of the idioms in this section are robust and recommendable.
 From C++17 variables can be marked as `inline`, ensuring that there is only a
 single copy of the variable. When used with `constexpr` to ensure safe
 initialization and destruction this gives another way to define a constant whose
-value is accessible at compile time.
+value is accessible at compile time. See [Tip #168](/tips/168) for more
+information.
 
 
 // in foo.h
diff --git a/_posts/2023-01-19-totw-218.md b/_posts/2023-01-19-totw-218.md
index 72b1e74..832b9a4 100644
--- a/_posts/2023-01-19-totw-218.md
+++ b/_posts/2023-01-19-totw-218.md
@@ -239,6 +239,6 @@ extension point, FTADLE comes highly recommended.
     [IFNDR](https://en.cppreference.com/w/cpp/language/ndr),
     [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern),
     and [SFINAE](https://en.cppreference.com/w/cpp/language/sfinae). We have
-    been pronouncing FTADLE as "fftah-dill" (similar to "battle" but with the
-    'b' replaced by the sound at the end of "raft"), but we encourage you to
+    been pronouncing FTADLE as "fftah-dill" (similar to "paddle" but with the
+    'p' replaced by the sound at the end of "raft"), but we encourage you to
     pronounce it in whichever way brings you the most joy.
diff --git a/_posts/2024-06-25-totw-197.md b/_posts/2024-06-25-totw-197.md
new file mode 100644
index 0000000..bc470f4
--- /dev/null
+++ b/_posts/2024-06-25-totw-197.md
@@ -0,0 +1,103 @@
+---
+title: "Tip of the Week #197: Reader Locks Should Be Rare"
+layout: tips
+sidenav: side-nav-tips.html
+published: true
+permalink: tips/197
+type: markdown
+order: "197"
+---
+
+Originally posted as TotW #197 on July 29, 2021
+
+*By [Titus Winters](mailto:titus@cs.ucr.edu)*
+
+Updated 2024-04-01
+
+Quicklink: [abseil.io/tips/197](https://abseil.io/tips/197)
+
+
+“Ah, how good it is to be among people who are reading.” - *Rainer Maria Rilke*
+
+The `absl::Mutex` class has supported two styles of locking for many years now:
+
+*   Exclusive locks, in which exactly one thread holds the lock.
+*   Shared locks, which have two modes. If they are held “for writing” they use
+    an exclusive lock, but they also have a different mode in which many threads
+    can hold the lock “for reading.”
+
+How can a shared lock be acceptable? Isn’t the whole point of having a lock to
+gain exclusive access to an object? The perceived value in shared locks is when
+we need read-only access to the underlying data/objects. Remember that we get
+data races and API races when two threads access the same data without
+synchronization, and at least one of those accesses is a write. If we use a
+shared-lock when many threads only need to read data, and always use exclusive
+locks when writing data, we can avoid contention among the readers and still
+avoid data and API races.
+
+To support this, `absl::Mutex` has both `Mutex::Lock()` (and
+`Mutex::WriterLock()`, an alternate name for the same exclusive behavior) as
+well as `Mutex::ReaderLock()`. From reading through those interfaces, you might
+think that we should prefer `ReaderLock()` when we’re only reading from the data
+protected by the lock.
+
+In many cases you’d be wrong.
+
+### ReaderLock Is Slow
+
+`ReaderLock` inherently does more bookkeeping and requires more overhead than a
+standard exclusive lock. As a result, in many cases using the more specialized
+form (shared locks) is actually a performance loss, as we have to do quite a bit
+more work in the lock machinery itself. This cost is minor in the absence of
+contention, but `ReaderLock` underperforms `Lock` under contention for short
+critical sections. Without contention, the value `ReaderLock` provides is less
+significant in the first place.
+
+Consider the logic in an exclusive lock vs. a shared lock. A shared lock
+generally must also have an exclusive lock mode - if there are no writers, no
+data race can occur, and thus there is no need for locking in the first place.
+Shared locking is therefore inherently more complex, requiring checks on whether
+other readers hold locks, or modifications to the (atomic) count of readers,
+etc.
+
+### When are Shared Locks Useful?
+
+Shared locks are primarily a benefit when the lock is going to be held for a
+comparatively long time and it's likely that multiple readers will concurrently
+obtain the *shared* lock. For example, if you’re going to do a lot of work while
+holding the lock (e.g. iterating over a large container, not just doing a single
+lookup), then a shared locking scheme may be valuable. The dominant question is
+not “am I writing to the data”, it’s “how long do I expect the lock to be held
+by readers (compared to how long it takes to acquire the lock)?”
+
+
+// This is bad - the amount of work done under the lock is insignificant.
+// The added complexity of using reader locks is going to cost more in aggregate
+// than the contention saved by having multiple threads able to call this
+// function concurrently.
+int Foo::GetElementSize() const {
+  absl::ReaderMutexLock l(&lock_);
+  return element_size_;
+}
+
+ +Even when the amount of computation performed under a lock is larger, and reader +locks become more useful, we often find we have better special-case interfaces +to avoid contention entirely - see https://abseil.io/fast and +https://abseil.io/docs/cpp/guides/synchronization for more. RCU (“Read Copy +Update”) abstractions provide a particularly common solution here, making the +read path essentially free. + +### What Should We Do? + +Be on the lookout for use of `ReaderLock` - the overwhelming majority of uses of +it are actually a pessimization … but we can’t statically determine that +definitively to rewrite code to use exclusive locking instead. (Reasoning about +concurrency properties in C++ is still too hard for most refactoring work.) + +If you spot `ReaderLock`, especially new uses of it, try to ask “Is the +computation under this lock often long?” If it’s just looking up a value in a +container, an exclusive lock is almost certainly a better solution. + +In the end, profiling may be the only way to be sure - contention tracking is +particularly valuable here.