patterncppMinor
The dumbest (futex-based) mutex
Viewed 0 times
themutexbasedfutexdumbest
Problem
After reading Ulrich Drepper's "Futexes are Tricky", I have written the following "dumbest mutex" in C++14 using the Linux
Therefore this is not a good mutex if you care about performance.
My question is: Leaving aside the performance issue, is this a correct mutex? Or does it, like most hand-written concurrency code, have some subtle bug?
Stylistic comments are also welcome.
futex primitives. This mutex is simpler than Drepper's; I believe it to be correct. The reason it gets to be so simple is the same as the reason I'm calling it the "dumbest mutex": it makes a system call on every unlock, even if the mutex is not being contended and nobody's waiting for the lock.Therefore this is not a good mutex if you care about performance.
My question is: Leaving aside the performance issue, is this a correct mutex? Or does it, like most hand-written concurrency code, have some subtle bug?
Stylistic comments are also welcome.
#include
#include
#include
#include
inline int futex_wait(void *addr, int block_if_value_is) {
return syscall(SYS_futex, addr, block_if_value_is, nullptr, nullptr, 0);
}
inline int futex_wake_one(void *addr) {
return syscall(SYS_futex, addr, FUTEX_WAKE, 1, nullptr, nullptr, 0);
}
class mutex {
std::atomic m_state;
static constexpr int UNLOCKED = 0;
static constexpr int LOCKED = 1;
public:
constexpr mutex() noexcept : m_state(UNLOCKED) {}
bool try_lock() {
return m_state.exchange(LOCKED) == UNLOCKED;
}
void lock() {
while (m_state.exchange(LOCKED) != UNLOCKED) {
futex_wait(&m_state, LOCKED);
}
}
void unlock() {
m_state = UNLOCKED;
futex_wake_one(&m_state);
}
};Solution
is this a correct mutex?
It is not correct. There is a requirement for std::mutex::lock (http://en.cppreference.com/w/cpp/thread/mutex/lock):
If another thread has already locked the mutex, a call to lock will
block execution until the lock is acquired.
A simple test below that uses your mutex consumes 100% CPU that means it is busy-waiting. I think you did it by mistake.
On the contrary if I use std::mutex for the same test consumes 0% CPU in waiting on mt.lock().
Below is a pidstat report for running a test with your mutex:
And this is a report for a test that uses std::mutex:
update
I believe your mistake is in parameters for syscall. FUTEX_WAKE on my Linux is equal to 1. But take a look at:
It passes
So you do not wait. You actually ask the kernel to wake up 0 (nullptr) processes. That is why you instead of waiting in kernel on your futex returns:
It is not correct. There is a requirement for std::mutex::lock (http://en.cppreference.com/w/cpp/thread/mutex/lock):
If another thread has already locked the mutex, a call to lock will
block execution until the lock is acquired.
A simple test below that uses your mutex consumes 100% CPU that means it is busy-waiting. I think you did it by mistake.
On the contrary if I use std::mutex for the same test consumes 0% CPU in waiting on mt.lock().
mutex mt;
int main() {
auto f = []() {
mt.lock();
mt.unlock();
};
mt.lock();
std::thread t (f);
std::this_thread::sleep_for(std::chrono::seconds(60));
mt.unlock();
t.join();
}Below is a pidstat report for running a test with your mutex:
$ pidstat 1 -p $(pidof custom_mutex)
Linux 3.10.0-229.el7.x86_64 (sk71.net.billing.ru) 05/16/2017 _x86_64_ (2 CPU)
09:44:31 AM UID PID %usr %system %guest %CPU CPU Command
09:44:32 AM 1000 9806 11.00 88.00 0.00 99.00 0 custom_mutex
09:44:33 AM 1000 9806 14.00 86.00 0.00 100.00 0 custom_mutex
09:44:34 AM 1000 9806 13.00 86.00 0.00 99.00 0 custom_mutex
09:44:35 AM 1000 9806 15.00 84.00 0.00 99.00 0 custom_mutex
09:44:36 AM 1000 9806 15.00 83.00 0.00 98.00 0 custom_mutex
09:44:37 AM 1000 9806 12.00 87.00 0.00 99.00 0 custom_mutex
09:44:38 AM 1000 9806 16.00 83.00 0.00 99.00 0 custom_mutex
09:44:39 AM 1000 9806 15.00 85.00 0.00 100.00 0 custom_mutexAnd this is a report for a test that uses std::mutex:
$ pidstat 1 -p $(pidof std_mutex)
Linux 3.10.0-229.el7.x86_64 (sk71.net.billing.ru) 05/16/2017 _x86_64_ (2 CPU)
09:46:45 AM UID PID %usr %system %guest %CPU CPU Command
09:46:46 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:47 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:48 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:49 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:50 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:51 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:52 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:53 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutexupdate
I believe your mistake is in parameters for syscall. FUTEX_WAKE on my Linux is equal to 1. But take a look at:
inline int futex_wait(void *addr, int block_if_value_is) {
return syscall(SYS_futex, addr, block_if_value_is, nullptr, nullptr, 0);
}It passes
block_if_value_is to SYS_futex as a parameter for int op. However the value of block_if_value_is in your program is equal to LOCKED and LOCKED is 1 (it means that it is equal to FUTEX_WAKE):static constexpr int UNLOCKED = 0;
static constexpr int LOCKED = 1;
futex_wait(&m_state, LOCKED);So you do not wait. You actually ask the kernel to wake up 0 (nullptr) processes. That is why you instead of waiting in kernel on your futex returns:
return syscall(SYS_futex, addr, block_if_value_is, nullptr, nullptr, 0);Code Snippets
mutex mt;
int main() {
auto f = []() {
mt.lock();
mt.unlock();
};
mt.lock();
std::thread t (f);
std::this_thread::sleep_for(std::chrono::seconds(60));
mt.unlock();
t.join();
}$ pidstat 1 -p $(pidof custom_mutex)
Linux 3.10.0-229.el7.x86_64 (sk71.net.billing.ru) 05/16/2017 _x86_64_ (2 CPU)
09:44:31 AM UID PID %usr %system %guest %CPU CPU Command
09:44:32 AM 1000 9806 11.00 88.00 0.00 99.00 0 custom_mutex
09:44:33 AM 1000 9806 14.00 86.00 0.00 100.00 0 custom_mutex
09:44:34 AM 1000 9806 13.00 86.00 0.00 99.00 0 custom_mutex
09:44:35 AM 1000 9806 15.00 84.00 0.00 99.00 0 custom_mutex
09:44:36 AM 1000 9806 15.00 83.00 0.00 98.00 0 custom_mutex
09:44:37 AM 1000 9806 12.00 87.00 0.00 99.00 0 custom_mutex
09:44:38 AM 1000 9806 16.00 83.00 0.00 99.00 0 custom_mutex
09:44:39 AM 1000 9806 15.00 85.00 0.00 100.00 0 custom_mutex$ pidstat 1 -p $(pidof std_mutex)
Linux 3.10.0-229.el7.x86_64 (sk71.net.billing.ru) 05/16/2017 _x86_64_ (2 CPU)
09:46:45 AM UID PID %usr %system %guest %CPU CPU Command
09:46:46 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:47 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:48 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:49 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:50 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:51 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:52 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutex
09:46:53 AM 1000 9965 0.00 0.00 0.00 0.00 0 std_mutexinline int futex_wait(void *addr, int block_if_value_is) {
return syscall(SYS_futex, addr, block_if_value_is, nullptr, nullptr, 0);
}static constexpr int UNLOCKED = 0;
static constexpr int LOCKED = 1;
futex_wait(&m_state, LOCKED);Context
StackExchange Code Review Q#163367, answer score: 9
Revisions (0)
No revisions yet.