[go: nahoru, domu]

Skip to content

Commit

Permalink
Export of internal doc changes to C++ Tips:
Browse files Browse the repository at this point in the history
rpc://team/absl-team/Abseil-Docs

Included changes:

647738412(shreck):	Internal change
647719902(shreck):	Fix malformed tag
647718693(shreck):	Internal change

645068012(Abseil Team):	Internal change

644392454(jdennett):	Internal change

643172690(Abseil Team):	Internal change

640698746(jdennett):	Internal change

634844740(Abseil Team):	Internal change

628148022(Abseil Team):	Internal change

628136192(Abseil Team):	Internal change

628059126(Abseil Team):	Internal change

620995142(Abseil Team):	Internal change

PiperOrigin-RevId: 647738412
Change-Id: I9d5ba5526c6fef527527f69938bcc446536122e6
  • Loading branch information
Abseil Team authored and manshreck committed Jun 28, 2024
1 parent 6d09216 commit 473676a
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 13 deletions.
17 changes: 8 additions & 9 deletions _posts/2019-11-25-totw-161.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ return SomeExpression(args);
#### Inline Expressions Under Test Into GoogleTest's <code>EXPECT_THAT</code>

<pre class="prettyprint lang-cpp bad-code">
auto actual = SortedAges(args);
std::vector&lt;string&gt; actual = SortedAges(args);
EXPECT_THAT(actual, ElementsAre(21, 42, 63));
</pre>

Expand All @@ -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)
Expand Down Expand Up @@ -129,17 +132,13 @@ three fields of the same proto message. Using a local variable to alias the
relevant message can clean it up:

<pre class="prettyprint lang-cpp code">
auto& subsubmessage = *myproto.mutable_submessage()-&gt;mutable_subsubmessage();
SubSubMessage& subsubmessage =
*myproto.mutable_submessage()-&gt;mutable_subsubmessage();
subsubmessage.set_foo(21);
subsubmessage.set_bar(42);
subsubmessage.set_baz(63);
</pre>

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
Expand All @@ -163,8 +162,8 @@ in C++11 we could write

<pre class="prettyprint lang-cpp code">
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 &lt; 18) children.insert(name);
Expand Down
2 changes: 1 addition & 1 deletion _posts/2019-12-12-totw-146.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ to *provide* the definition via `=default`. For example:

<pre class="prettyprint lang-cpp code">
struct Foo {
Foo() = default; // "Used-declared", NOT "user-provided".
Foo() = default; // "User-declared", NOT "user-provided".

int v;
};
Expand Down
3 changes: 2 additions & 1 deletion _posts/2020-09-01-totw-140.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<pre class="prettyprint lang-cpp code">
// in foo.h
Expand Down
4 changes: 2 additions & 2 deletions _posts/2023-01-19-totw-218.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
103 changes: 103 additions & 0 deletions _posts/2024-06-25-totw-197.md
Original file line number Diff line number Diff line change
@@ -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)?”

<pre class="prettyprint lang-cpp bad-code">
// 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_;
}
</pre>

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.

0 comments on commit 473676a

Please sign in to comment.