Date: Tue, 29 Apr 2025 15:30:20 +0100
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
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
Received on 2025-04-29 14:30:30