C++ Logo

std-discussion

Advanced search

Re: shared_mutex halting problem

From: Jennifier Burnett <jenni_at_[hidden]>
Date: Tue, 29 Apr 2025 17:23:54 +0100
Looking up the behaviour for Window's SRW locks, and beyond finding that they similarly don't make any guarantees about the order of readers and writers I also found the following:

https://devblogs.microsoft.com/oldnewthing/20170705-00/?p=96535

The blog claims: "That said, SRW locks aren’t aggressively unfair either. If shared readers arrive when there is a waiting writer, they will queue up behind the writer, which generally avoids the problem of readers starving out a writer."

I don't have immediate access to a windows machine so I can't test the current behaviour but if that blog is to be believed then Windows did at one point block readers for waiting writers.

Either way it does look like the standard's wording is intentionally vague to cover all implementations of the OS primitives it's targeting

On 29 April 2025 16:49:38 BST, Nate Eldredge via Std-Discussion <std-discussion_at_[hidden]> wrote:
>At the very least, I don't see a clear unambiguous statement that the program *cannot* block in this situation.
>
>It's worth noting that POSIX pthread rwlocks explicitly allow blocking in this case (https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/): "If the Thread Execution Scheduling option is not supported, it is implementation-defined whether the calling thread acquires the lock when a writer does not hold the lock and there are writers blocked on the lock." (When Thread Execution Scheduling is supported, they give conditions under which an attempt to lock for reading *must* block when a writer is already blocked.) I think it's unlikely that the ISO C++ committee would have deliberately imposed requirements that contradicted those of one of the most prominent potential implementations, at least not without saying so very clearly.
>
>The advantage of making the reader block is that it avoids a starvation scenario, where so many readers are using the data that there is always at least one reader holding the lock, even though each one needs it for only a short time. Then a writer may be starved out indefinitely, unless the implementation deliberately blocks new readers from taking the lock until the writer gets a chance to hold it.
>
>
>> On Apr 29, 2025, at 08:30, Lénárd Szolnoki via Std-Discussion <std-discussion_at_[hidden]> wrote:
>>
>> Hi,
>>
>> Consider the following program:
>>
>> #include <shared_mutex>
>> #include <mutex>
>> #include <thread>
>> #include <barrier>
>> #include <iostream>
>>
>> int main() {
>> std::shared_mutex smtx;
>> std::barrier b(2);
>> auto reader_1 = std::jthread([&]{
>> auto lock = std::shared_lock{smtx};
>> std::cout << "shared lock acquired\n";
>> b.arrive_and_wait();
>> });
>>
>> std::this_thread::sleep_for(std::chrono::seconds(1));
>>
>> auto writer = std::jthread([&]{
>> auto lock = std::unique_lock{smtx};
>> std::cout << "unique lock acquired\n";
>> });
>>
>> std::this_thread::sleep_for(std::chrono::seconds(1));
>>
>> auto reader_2 = std::jthread([&]{
>> auto lock = std::shared_lock{smtx};
>> std::cout << "shared lock acquired\n";
>> b.arrive_and_wait();
>> });
>> }
>>
>> Is this program guaranteed to terminate, assuming the implementation provides concurrent progress guarantees? The behaviour is different for libstdc++ and libc++:
>>
>> https://godbolt.org/z/vzdYnvGPr
>>
>> Seemingly libc++ gets deadlocked in the following configuration:
>> * reader_1 is blocked on b.arrive_and_wait()
>> * writer is blocked on smtx.lock()
>> * reader_2 is blocked on smtx.lock_shared()
>>
>> I think it's questionable whether the standard allows smtx.lock_shared() to block while no thread holds exclusive ownership over smtx.
>>
>> Relevant wording:
>>
>> https://eel.is/c++draft/thread.mutex#thread.sharedmutex.requirements.general-5
>>
>> "Blocks the calling thread until shared ownership of the mutex can be obtained for the calling thread."
>>
>> My reading of "shared ownership of the mutex can be obtained": if shared ownership would be acquired for the current execution agent, then the condition described in the paragraph [thread.sharedmutex.requirements.general]/2 would hold.
>>
>> https://eel.is/c++draft/thread.mutex#thread.sharedmutex.requirements.general-2
>>
>> "Multiple execution agents can simultaneously hold a shared lock ownership of a shared mutex type. But no execution agent holds a shared lock while another execution agent holds an exclusive lock on the same shared mutex type, and vice-versa."
>>
>> This suggests that if there is no exclusive lock on smtx then smtx.lock_shared() in reader_2 does not block.
>>
>> Is my reading correct?
>>
>> Is this a bug in libc++ or a defect in the standard? Notably the the reference implementation in N2406 also has the same blocking behaviour.
>>
>> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html
>>
>> Cheers,
>> Lénárd
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2025-04-29 16:24:04