std::once_flag is a glass hill
source link: https://quuxplusone.github.io/blog/2020/10/23/once-flag/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
std::once_flag
is a glass hill
Yesterday I did a followup Q&A session for my CppCon 2020 presentation
“Back to Basics: Concurrency.”
(These followup sessions aren’t recorded.) During the Q&A, there was some discussion
of std::once_flag
. Let’s repeat my table of the three C++11 synchronization
primitives (which was briefly flashed as
slide 34 in the recorded talk,
but I was running behind at that point and didn’t stop to discuss it):
mutex
condition_variable
once_flag
lock
blocks only if someone “owns” the mutex.
wait
always blocks.
call_once
blocks only if the “done” flag isn’t yet set.
Many threads can queue up on lock
.
Many threads can queue up on wait
.
Many threads can queue up on call_once
.
Calling unlock
unblocks exactly one waiter: the new “owner.”
Calling notify_one
unblocks exactly one waiter.
Failing at the callback unblocks exactly one waiter: the new “owner.”
Calling notify_all
unblocks all waiters.
Succeeding at the callback unblocks all waiters and sets the “done” flag.
Now, the weird thing about once_flag
is that its sole method, call_once
, is
not implemented as a C++ member function; it’s implemented as a free function
that takes its “this
parameter” explicitly.
If you’ve seen me speak on synchronization primitives before, you know
I think this is a super weird glitch in the C++11 concurrency API.
Incidentally, you can also write
std::lock(myMutex);
as a free function, but there is no correspondingstd::unlock
!
Are you surprised at the complexity of the third column there? A lot of people
think of once_flag
as a simple primitive. (Or perhaps I should say, they think
of call_once
as a simple primitive, and don’t even realize that the proper object of
our attention is once_flag
itself!) My mental model of once_flag
took a while
to develop. Here’s how I think of it now:
once_flag
is a glass hill
Andrew Lang’s Blue Fairy Book tells the story of “The Princess on the Glass Hill.”
Close to the King’s palace there was a high, high hill of glass, slippery as ice. Upon the very top of this sat the King’s daughter with three golden apples in her lap, and the man who could ride up and take the three golden apples should marry her and have half the kingdom.
Ignore the apples. They’re not part of this metaphor.
The important thing is that we’ve set a task to be accomplished; and in fact it can be accomplished by only one individual (you can’t have two knights marrying the princess!); and, importantly, it is also possible for an individual to fail at the task.
And so we end up with a line of knights at the base of the hill, each one waiting his turn to try the challenge. One knight tries the hill at a time; if he slides back down, he goes home in disgrace and the next one tries. (If, having failed once, a knight wants to come back the next day and try again, that’s totally fine.)
As soon as one knight succeeds, he marries the princess and everyone else can go on about their business.
This being C++, our “task” is expressed as a function (or a lambda,
which is the same thing); and “success” means executing the function
all the way to completion. “Failure” means that the function didn’t
complete because it threw an exception. Throwing an exception is the
only way to “fail,” as far as once_flag
is concerned.
In code, our metaphor looks something like this:
struct Princess {};
Princess getPrincess(int id) {
if (std::random_device()() % 10) {
print("Knight ", id, " has slid back down the hill!\n");
throw std::runtime_error("failed");
}
return Princess{};
}
void goQuesting(int id, std::once_flag& hill) {
std::optional<Princess> wife;
print("Knight ", id, " has arrived at the foot of the hill.\n");
try_again:
try {
std::call_once(hill, [&]() {
print("Knight ", id, " is riding up the hill.\n");
wife = getPrincess(id);
print("Knight ", id, " has won the princess!\n");
});
} catch (...) {
print("Knight ", id, " is ready to try again.\n");
goto try_again;
}
print("Knight ", id, " has gone home ", (wife ? "with" : "without"), " a wife.\n");
}
int main() {
std::vector<std::thread> knights;
std::once_flag hill;
for (int id=0; id < 4; ++id) {
knights.emplace_back([id, &hill]() {
goQuesting(id, hill);
});
}
for (auto&& t : knights) t.join();
}
Godbolt Compiler Explorer can’t run multithreaded code as of this writing, but here is the code on Godbolt, anyway.
Static initializers are glass hills (with shared objectives)
Reminder: C++11 also introduced “thread-safe static initialization,” which means
that static initializer expressions are also glass hills in the same sense as
once_flag
.
void goQuesting2(int id, std::once_flag& hill) {
print("Knight ", id, " has arrived at the foot of the hill.\n");
static std::optional<Princess> wife = [&]() {
print("Knight ", id, " is riding up the hill.\n");
auto p = getPrincess(id);
print("Knight ", id, " has won the princess!\n");
return p;
}();
print("Knight ", id, " has gone home ", (wife ? "with" : "without"), " a wife.\n");
}
The difference here is that the static
local variable is shared by all
callers of goQuesting2
; so as soon as one knight succeeds at riding up
the glass hill, all remaining knights immediately go home with a wife.
Maybe the knights are all in the service of the same prince, and they’re
bringing him his wife; or make up your own story, I don’t care.
Anyway, observe that the knights in this case still queue up at the foot of the hill, and it’s possible for N-1 of the threads to be blocked waiting while some other thread tackles the initialization task.
By the way, if you’re using some greenthread-esque programming framework
where blocking any single std::thread
could cause deadlock, I suppose you
should carefully audit your use of non-constinit
static initializers.
Recommend
-
7
Hill's Pet宠物食品:别让你的宠物长太胖了 ...
-
2
Struct std::sync::Once1.0.0[−]
-
6
Episode 193 – Software Coaching with GeePaw HillPodcast: Play in new window |
-
4
Congrats to compiler team member Aaron Hill Apr. 26, 2021 · Wesley Wiser on behalf of the compiler team I am pleased to a...
-
7
Global Marketing Strategy McGraw Hill PDF C872f8d1e May 18, 2021 | By tiffany | Filed in:
-
3
Galaxy Digital收购资产管理公司Vision Hill • 8 小时前 碳链价值APP讯,加密货币金融服务公司Galaxy Digital周一宣布以未披露的价格收购了资产...
-
5
PointsBet vs William Hill – Mana Yang Lebih Baik? Jika Anda cukup beruntung untuk hidup di negara bagian dengan lawful judi olahraga, maka Anda mungkin memiliki sejumlah opsi untuk dip...
-
3
HomeDefaultWilliam Hill Sportsbook In-Arena Pertama di AS dengan Pembukaan DC William Hill Sport...
-
4
Funding INKR Raises $3.1 Mn In Pre-Series A Round Led By Monk's Hill Ventures The platform has grown 200 per cent MAU si...
-
3
Know about Spring Hill Carpet Cleaning Company
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK