Reputation: 2524
As a learning exercise, I'm just trying to make a Semaphore
class using std::mutex
and a few other things provided by the C++ standard. My semaphore should allow as many readLock()
as needed, however a writeLock()
can only be acquired after all reads are unlocked.
//Semaphore.h
#include <mutex>
#include <condition_variable>
class Semaphore{
public:
Semaphore();
void readLock(); //increments the internal counter
void readUnlock(); //decrements the internal counter
void writeLock(); //obtains sole ownership. must wait for count==0 first
void writeUnlock(); //releases sole ownership.
int count; //public for debugging
private:
std::mutex latch;
std::unique_lock<std::mutex> lk;
std::condition_variable cv;
};
//Semaphore.cpp
#include "Semaphore.h"
#include <condition_variable>
#include <iostream>
using namespace std;
Semaphore::Semaphore() : lk(latch,std::defer_lock) { count=0; }
void Semaphore::readLock(){
latch.lock();
++count;
latch.unlock();
cv.notify_all(); //not sure if this needs to be here?
}
void Semaphore::readUnlock(){
latch.lock();
--count;
latch.unlock();
cv.notify_all(); //not sure if this needs to be here?
}
void Semaphore::writeLock(){
cv.wait(lk,[this](){ return count==0; }); //why can't std::mutex be used here?
}
void Semaphore::writeUnlock(){
lk.unlock();
cv.notify_all();
}
My test program will writeLock()
the semaphore, start a bunch of threads, and then release the semaphore. Immediately afterwards, the main thread will attempt to writeLock()
the semaphore again. The idea is that when the semaphore becomes unlocked, the threads will readLock()
it and prevent the main thread from doing anything until they all finish. When they all finish and release the semaphore, then the main thread can acquire access again. I realize this may not necessarily happen, but it's one of the cases I'm looking for.
//Main.cpp
#include <iostream>
#include <thread>
#include "Semaphore.h"
using namespace std;
Semaphore s;
void foo(int n){
cout << "Thread Start" << endl;
s.readLock();
this_thread::sleep_for(chrono::seconds(n));
cout << "Thread End" << endl;
s.readUnlock();
}
int main(){
std::srand(458279);
cout << "App Launch" << endl;
thread a(foo,rand()%10),b(foo,rand()%10),c(foo,rand()%10),d(foo,rand()%10);
s.writeLock();
cout << "Main has it" << endl;
a.detach();
b.detach();
c.detach();
d.detach();
this_thread::sleep_for(chrono::seconds(2));
cout << "Main released it" << endl;
s.writeUnlock();
s.writeLock();
cout << "Main has it " << s.count << endl;
this_thread::sleep_for(chrono::seconds(2));
cout << "Main released it" << endl;
s.writeUnlock();
cout << "App End" << endl;
system("pause"); //windows, sorry
return 0;
}
The program throws an exception saying "unlock of unowned mutex". I think the error is in writeLock()
or writeUnlock()
, but I'm not sure. Can anyone point me in the right direction?
EDIT: There was a std::defer_lock
missing when initializing lk
in the constructor, however it didn't fix the error I was getting. As mentioned in the comment, this isn't a semaphore and I apologize for the confusion. To reiterate the problem, here is the output that I get (things in parenthesis are just my comments and not actually in the output):
App Launch
Thread Start
Thread Start
Main has it
Thread Start
Thread Start
Thread End (what?)
Main released it
f:\dd\vctools\crt_bld\self_x86\crt\src\thr\mutex.c(131): unlock of unowned mutex
Thread End
Thread End
Thread End
Upvotes: 4
Views: 7247
Reputation: 171253
This is definitely not a "semaphore".
Your Semaphore
constructor acquires the lock on latch
right away, then you unlock it twice because writeUnlock()
calls lk.unlock()
and the next call to writeLock()
tries to wait on a condition variable with an unlocked mutex, which is undefined behaviour, then you the next call to writeUnlock()
tries to unlock an unlocked mutex, which is also undefined behaviour.
Are you sure the constructor should lock the mutex right away? I think you want to use std::defer_lock
in the constructor, and then lock the mutex in writeLock()
.
Upvotes: 3