I’m learning about atomics and the futex syscall; and am trying to implement my own (simple) lock for fun.
The implementation is below and there seems to be an issue.
I would expect the global value to always be 200,000 , however while running the test it’ll, on a few runs, output 199,999.
My only initial guess is that one of the threads isn’t properly flushing it’s updated value of “global” to main-memory. However all atomic operations are sequentially-consistent which makes me rule this out.
Please let me know if you see any issues.
class Mutex2 {
public:
Mutex2() : mLock(0) {}
void lock() {
int expected = 0;
if (!atomic_compare_exchange_strong(&mLock, &expected, 1)) { // Attempt to acquire lock.
// Failed to acquire lock.
do {
// Go to sleep. Blocks...
// futex / FUTEX_WAIT does a load and compare atomically.
long retVal = syscall(SYS_futex, (uint32_t*) &mLock, FUTEX_WAIT, 1, nullptr, nullptr, nullptr);
// Either we were woken up during unlock() or we never slept,
// in which case, attempt to get the lock again.
} while (!atomic_compare_exchange_strong(&mLock, &expected, 1));
// Lock acquired.
}
}
void unlock() {
std::atomic_store(&mLock, 0);
syscall(SYS_futex, (uint32_t*) &mLock, FUTEX_WAKE, 1, nullptr, nullptr, nullptr);
}
private:
std::atomic_int mLock; // 0 means unlocked. 1 means locked.
};
// ===== TEST =====
//
Mutex2 mutex2
static int global = 0;
int main(int argc, char** argv) {
// Create two pthreads
// Make both increment a shared integer 100,000 times.
for (int i=0; i<10000; i++) {
pthread_t threadTwo;
pthread_create(&threadTwo, NULL, addTest, NULL);
addTest(NULL);
pthread_join(threadTwo, NULL);
if (global != 200000)
printf("Global value: %dn", global);
global = 0;
}
return 0;
}
void* addTest(void*) {
const int INCREMENTS = 100000;
for (int i=0; i<INCREMENTS; i++) {
mutex2.lock();
global++;
mutex2.unlock();
}
return NULL;
}