As an educational exercise I'm implementing a thread pool using condition variables. A controller thread creates a pool of threads that wait on a signal (an atomic variable being set to a value above zero). When signaled the threads wake, perform their work, and when the last thread is done it signals the main thread to awaken. The controller thread blocks until the last thread is complete. The pool is then available for subsequent re-use.
Every now and then I was getting a timeout on the controller thread waiting for the worker to signal completion (likely because of a race condition when decrementing the active work counter), so in an attempt to solidify the pool I replaced the "wait(lck)" form of the condition variable's wait method with "wait(lck, predicate)". Since doing this, the behaviour of the thread pool is such that it seems to permit decrementing of the active work counter below 0 (which is the condition for reawakening the controller thread) - I have a race condition. I've read countless articles on atomic variables, synchronisation, memory ordering, spurious and lost wakeups on stackoverflow and various other sites, have incorporated what I've learnt to the best of my ability, and still cannot for the life of me work out why the way I've coded the predicated wait just does not work. The counter should only ever be as high as the number of threads in the pool (say, 8) and as low as zero. I've started losing faith in myself - it just shouldn't be this hard to do something fundamentally simple. There is clearly something else I need to learn here :)
Considering of course that there was a race condition I ensured that the two variables that drive the awakening and termination of the pool are both atomic, and that both are only ever changed while protected with a unique_lock. Specifically, I made sure that when a request to the pool was launched, the lock was acquired, the active thread counter was changed from 0 to 8, unlocked the mutex, and then "notified_all". The controller thread would only then be awakened with the active thread count at zero, once the last worker thread decremented it that far and "notified_one".
In the worker thread, the condition variable would wait and wake only when the active thread count is greater than zero, unlock the mutex, in parallel proceed to execute the work preassigned to the processor when the pool was created, re-acquire the mutex, and atomically decrement the active thread count. It would then, while still supposedly protected by the lock, test if it was the last thread still active, and if so, again unlock the mutex and "notify_one" to awaken the controller.
The problem is - the active thread counter repeatedly proceeds below zero after even only 1 or 2 iterations. If I test the active thread count at the start of a new workload, I could find the active thread count down around -6 - it is as if the pool was allowed to reawaken the controller thread before the work was completed.
Given that the thread counter and terminate flag are both atomic variables and are only ever modified while under the protection of the same mutex, I am using sequential memory ordering for all updates, I just cannot see how this is happening and I'm lost.
#include <stdafx.h>
#include <Windows.h>
#include <iostream>
#include <thread>
using std::thread;
#include <mutex>
using std::mutex;
using std::unique_lock;
#include <condition_variable>
using std::condition_variable;
#include <atomic>
using std::atomic;
#include <chrono>
#include <vector>
using std::vector;
class IWorkerThreadProcessor
{
public:
virtual void Process(int) = 0;
};
class MyProcessor : public IWorkerThreadProcessor
{
int index_ = 0;
public:
MyProcessor(int index)
{
index_ = index;
}
void Process(int threadindex)
{
for (int i = 0; i < 5000000; i++);
std::cout << '(' << index_ << ':' << threadindex << ") ";
}
};
#define MsgBox(x) do{ MessageBox(NULL, x, L"", MB_OK ); }while(false)
class ThreadPool
{
private:
atomic<unsigned int> invokations_ = 0;
//This goes negative when using the wait_for with predicate
atomic<int> threadsActive_ = 0;
atomic<bool> terminateFlag_ = false;
vector<std::thread> threads_;
atomic<unsigned int> poolSize_ = 0;
mutex mtxWorker_;
condition_variable cvSignalWork_;
condition_variable cvSignalComplete_;
public:
~ThreadPool()
{
TerminateThreads();
}
void Init(std::vector<IWorkerThreadProcessor*>& processors)
{
unique_lock<mutex> lck2(mtxWorker_);
threadsActive_ = 0;
terminateFlag_ = false;
poolSize_ = processors.size();
for (int i = 0; i < poolSize_; ++i)
threads_.push_back(thread(&ThreadPool::launchMethod, this, processors[i], i));
}
void ProcessWorkload(std::chrono::milliseconds timeout)
{
//Only used to see how many invocations I was getting through before experiencing the issue - sadly it's only one or two
invocations_++;
try
{
unique_lock<mutex> lck(mtxWorker_);
//!!!!!! If I use the predicated wait this break will fire !!!!!!
if (threadsActive_.load() != 0)
__debugbreak();
threadsActive_.store(poolSize_);
lck.unlock();
cvSignalWork_.notify_all();
lck.lock();
if (!cvSignalComplete_.wait_for(
lck,
timeout,
[this] { return threadsActive_.load() == 0; })
)
{
//As you can tell this has taken me through a journey trying to characterise the issue...
if (threadsActive_ > 0)
MsgBox(L"Thread pool timed out with still active threads");
else if (threadsActive_ == 0)
MsgBox(L"Thread pool timed out with zero active threads");
else
MsgBox(L"Thread pool timed out with negative active threads");
}
}
catch (std::exception e)
{
__debugbreak();
}
}
void launchMethod(IWorkerThreadProcessor* processor, int threadIndex)
{
do
{
unique_lock<mutex> lck(mtxWorker_);
//!!!!!! If I use this predicated wait I see the failure !!!!!!
cvSignalWork_.wait(
lck,
[this] {
return
threadsActive_.load() > 0 ||
terminateFlag_.load();
});
//!!!!!!!! Does not cause the failure but obviously will not handle
//spurious wake-ups !!!!!!!!!!
//cvSignalWork_.wait(lck);
if (terminateFlag_.load())
return;
//Unlock to parallelise the work load
lck.unlock();
processor->Process(threadIndex);
//Re-lock to decrement the work count
lck.lock();
//This returns the value before the subtraction so theoretically if the previous value was 1 then we're the last thread going and we can now signal the controller thread to wake. This is the only place that the decrement happens so I don't know how it could possibly go negative
if (threadsActive_.fetch_sub(1, std::memory_order_seq_cst) == 1)
{
lck.unlock();
cvSignalComplete_.notify_one();
}
else
lck.unlock();
} while (true);
}
void TerminateThreads()
{
try
{
unique_lock<mutex> lck(mtxWorker_);
if (!terminateFlag_)
{
terminateFlag_ = true;
lck.unlock();
cvSignalWork_.notify_all();
for (int i = 0; i < threads_.size(); i++)
threads_[i].join();
}
}
catch (std::exception e)
{
__debugbreak();
}
}
};
int main()
{
std::vector<IWorkerThreadProcessor*> processors;
for (int i = 0; i < 8; i++)
processors.push_back(new MyProcessor(i));
std::cout << "Instantiating thread pool\n";
auto pool = new ThreadPool;
std::cout << "Initialisting thread pool\n";
pool->Init(processors);
std::cout << "Thread pool initialised\n";
for (int i = 0; i < 200; i++)
{
std::cout << "Workload " << i << "\n";
pool->ProcessWorkload(std::chrono::milliseconds(500));
std::cout << "Workload " << i << " complete." << "\n";
}
for (auto a : processors)
delete a;
delete pool;
return 0;
}
class ThreadPool
{
private:
atomic<unsigned int> invokations_ = 0;
std::atomic<unsigned int> awakenings_ = 0;
std::atomic<unsigned int> startedWorkloads_ = 0;
std::atomic<unsigned int> completedWorkloads_ = 0;
atomic<bool> terminate_ = false;
atomic<bool> stillFiring_ = false;
vector<std::thread> threads_;
atomic<unsigned int> poolSize_ = 0;
mutex mtx_;
condition_variable cvSignalWork_;
condition_variable cvSignalComplete_;
public:
~ThreadPool()
{
TerminateThreads();
}
void Init(std::vector<IWorkerThreadProcessor*>& processors)
{
unique_lock<mutex> lck2(mtx_);
//threadsActive_ = 0;
terminate_ = false;
poolSize_ = processors.size();
for (int i = 0; i < poolSize_; ++i)
threads_.push_back(thread(&ThreadPool::launchMethod, this, processors[i], i));
awakenings_ = 0;
completedWorkloads_ = 0;
startedWorkloads_ = 0;
invokations_ = 0;
}
void ProcessWorkload(std::chrono::milliseconds timeout)
{
try
{
unique_lock<mutex> lck(mtx_);
invokations_++;
if (startedWorkloads_ != 0)
__debugbreak();
if (completedWorkloads_ != 0)
__debugbreak();
if (awakenings_ != 0)
__debugbreak();
if (stillFiring_)
__debugbreak();
stillFiring_ = true;
lck.unlock();
cvSignalWork_.notify_all();
lck.lock();
if (!cvSignalComplete_.wait_for(
lck,
timeout,
//[this] { return this->threadsActive_.load() == 0; })
[this] { return completedWorkloads_ == poolSize_ && !stillFiring_; })
)
{
if (completedWorkloads_ < poolSize_)
{
if (startedWorkloads_ < poolSize_)
MsgBox(L"Thread pool timed out with some threads unstarted");
else if (startedWorkloads_ == poolSize_)
MsgBox(L"Thread pool timed out with all threads started but not all completed");
}
else
__debugbreak();
}
if (completedWorkloads_ != poolSize_)
__debugbreak();
if (awakenings_ != poolSize_)
__debugbreak();
awakenings_ = 0;
completedWorkloads_ = 0;
startedWorkloads_ = 0;
}
catch (std::exception e)
{
__debugbreak();
}
}
void launchMethod(IWorkerThreadProcessor* processor, int threadIndex)
{
do
{
unique_lock<mutex> lck(mtx_);
cvSignalWork_.wait(
lck,
[this] {
return
(stillFiring_ && (startedWorkloads_ < poolSize_)) ||
terminate_;
});
awakenings_++;
if (startedWorkloads_ == 0 && terminate_)
return;
if (stillFiring_ && startedWorkloads_ < poolSize_) //guard against spurious wakeup
{
startedWorkloads_++;
if (startedWorkloads_ == poolSize_)
stillFiring_ = false;
lck.unlock();
processor->Process(threadIndex);
lck.lock();
completedWorkloads_++;
if (completedWorkloads_ == poolSize_)
{
lck.unlock();
cvSignalComplete_.notify_one();
}
else
lck.unlock();
}
else
lck.unlock();
} while (true);
}
void TerminateThreads()
{
try
{
unique_lock<mutex> lck(mtx_);
if (!terminate_) //Don't attempt to double-terminate
{
terminate_ = true;
lck.unlock();
cvSignalWork_.notify_all();
for (int i = 0; i < threads_.size(); i++)
threads_[i].join();
}
}
catch (std::exception e)
{
__debugbreak();
}
}
};
I'm not certain if the following helps solve the problem, but I think the error is as shown below:
This
if (!cvSignalComplete_.wait_for(
lck,
timeout,
[this] { return threadsActive_.load() == 0; })
)
should be replaced by
if (!cvSignalComplete_.wait_for(
lck,
timeout,
[&] { return threadsActive_.load() == 0; })
)
Looks like the lambda is not accessing the instantiated member of the class. Here is some reference to back my case. Look at Lambda Capture section of this page.
Edit:
Another place you are using wait for with lambdas.
cvSignalWork_.wait(
lck,
[this] {
return
threadsActive_.load() > 0 ||
terminateFlag_.load();
});
Maybe modify all the lambdas and then see if it works?
The reason I'm looking at the lambda is because it seems like a case similar to a spurious wakeup. Hope it helps.
Related
I am implementing the producer-consumer problem. To implement this, we need to have std::condition_variable along with std::mutex to notify threads to wake up. Using these 2 primitives, the producer can notify to consumer and vice-versa to wake up. This is generally required to avoid thread starvation issues. But I am thinking does this issue really persist in the case of the multiprocessors system?
This question comes because I am implementing this using lock-free ring buffer and I don't want to use std::mutex and std::condition_variable at the producer and consumer sides. Since this queue can't have a data-race issue calling enqueue() and dequeue(). Below is the code.
template<typename MessageType>
class MessageProcessor
{
public:
~MessageProcessor()
{
stop();
if (workerThread_.joinable())
workerThread_.join();
}
bool postMessage(MessageType const &msg)
{
return queue_.enqueue(msg);
}
void registerHandler(std::function<void(MessageType)> handler, int32_t coreId=-1, std::string_view const &name="")
{
std::call_once(init_, [&](){
handler_ = std::move(handler);
workerThread_ = std::thread{&MessageProcessor::process, this};
if (!setAffinity(coreId, workerThread_))
LOG("Msg Processing thread couldn't be pinned to core: " << coreId);
else
LOG("Msg Processing thread pinned to core: " << coreId);
if (! name.empty())
pthread_setname_np(workerThread_.native_handle(), name.data());
});
}
void stop()
{
stop_ = true;
}
private:
void process() //This is a consumer, runs in a separate thread
{
while(!stop_.load(std::memory_order_acquire))
{
MessageType msg;
if (! queue_.dequeue(msg))
continue;
try
{
handler_(msg);
}
catch(std::exception const &ex)
{
LOG("Error while processing data: " << msg << ", Exception: " << ex.what());
}
catch(...)
{
LOG("UNKOWN Error while processing data: " << msg);
}
}
}
bool setAffinity(int32_t const coreId, std::thread &thread)
{
int cpuCoreCount = __sysconf(_GLIBCXX_USE_SC_NPROCESSORS_ONLN);
if (coreId < 0 || coreId >= cpuCoreCount)
return false;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(coreId, &cpuset);
pthread_t currentThread = thread.native_handle();
return pthread_setaffinity_np(currentThread, sizeof(cpu_set_t), &cpuset) == 0;
}
std::thread workerThread_;
std::atomic<bool> stop_{false};
MPMC_Circular_Queue<MessageType, 1024> queue_;
std::function<void(MessageType)> handler_{};
std::once_flag init_;
};
int main()
{
pthread_setname_np(pthread_self(), "MAIN");
MessageProcessor<int> processor;
processor.registerHandler([](int i){
LOG("Received value: " << i);
}, 2, "PROCESSOR");
std::thread t1([&]() { //Producer thread1
for (int i = 1; i <= 100000; i += 2)
{
LOG("Submitting value: " << i);
processor.postMessage(i);
}
});
pthread_setname_np(t1.native_handle(), "ODD ");
std::thread t2([&]() { //Producer thread2
for (int i = 2; i <= 100000; i += 2)
{
LOG("Submitting value: " << i);
processor.postMessage(i);
}
});
pthread_setname_np(t2.native_handle(), "EVEN");
for (int i = 1; i <= 100000; ++i)
{
LOG("Runing main thread: " << i);
}
t1.join();
t2.join();
return 0;
}
Can this code raise thread starvation issue in modern multiprocessors system? MPMC_Circular_Queue is a lock free bounded queue.
I am trying to teach myself multithreading and I followed this tutorial here: https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/
If you scroll all the way to the bottom there is a sample snippet of a producer-consumer and it asks us to solve the race conditions found in this code:
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <queue>
using namespace std;
int main() {
int c = 0;
bool done = false;
queue<int> goods;
thread producer([&]() {
for (int i = 0; i < 500; ++i) {
goods.push(i);
c++;
}
done = true;
});
thread consumer([&]() {
while (!done) {
while (!goods.empty()) {
goods.pop();
c--;
}
}
});
producer.join();
consumer.join();
cout << "Net: " << c << endl;
}
The Net value at the end should be 0, here is my attempt at it:
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <queue>
#include <atomic>
using namespace std;
int main() {
int c = 0;
bool done = false;
queue<int> goods;
mutex mtx;
condition_variable cond_var;
// thread to produce 500 elements
thread producer([&]() {
for (int i = 0; i < 500; ++i) {
// lock critical secion
unique_lock<mutex> lock(mtx);
goods.push(i);
c++;
lock.unlock();
// notify consumer that data has been produced
cond_var.notify_one();
}
// notify the consumer that it is done
done = true;
cond_var.notify_one();
});
// thread to consume all elements
thread consumer([&]() {
while (!done) {
unique_lock<mutex> lock(mtx);
while (!goods.empty()) {
goods.pop();
c--;
}
// unlocks lock and wait until something in producer gets put
cond_var.wait(lock);
}
});
producer.join();
consumer.join();
cout << "Net: " << c << endl;
}
I feel like I am fundamentally missing something. I believe the biggest problem I am having is in the consumer with the cond_var.wait() because if the producer sets "done" to true then the consumer won't go back into the while(!goods.empty()). I am not sure how to fix it though.
Any hints, explanations or even different approaches would be appreciated!
Producer:
thread producer([&]() {
for (int i = 0; i < 500; ++i)
{
{
// Just have a lock while interacting with shared items.
unique_lock<mutex> lock(mtx);
goods.push(i);
c++;
}
cond_var.notify_one();
}
// Lock to update shared state.
unique_lock<mutex> lock(mtx);
done = true;
cond_var.notify_one();
});
Consumer
thread consumer([&]() {
// This loop exits when
// done => true
// AND goods.empty() => true
// Acquire lock before checking shared state.
unique_lock<mutex> lock(mtx);
while (!(done && goods.empty()))
{
// Wait until there is something in the queue to processes
// releasing lock while we wait.
// Break out if we are done or goods is not empty.
cond_var.wait(lock, [&](){return done || !goods.empty();});
// You now have the lock again, so modify shared state is allowed
// But there is a possibility of no goods being available.
// So let's check before doing work.
if (!goods.empty())
{
goods.pop();
c--;
}
}
});
Alternatively if we are simply solving for race condition. We can simply check on the state of done and make sure no other variables have interactions.
Producer:
thread producer([&]() {
// The consumer is not allowed to touch goods
// until you are finished. So just use with
// no locks.
for (int i = 0; i < 500; ++i)
{
goods.push(i);
c++;
}
// Lock to update shared state.
// Tell consumer we are ready for processing.
unique_lock<mutex> lock(mtx);
done = true;
cond_var.notify_one();
});
Consumer
thread consumer([&]() {
// Acquire lock before checking shared state.
unique_lock<mutex> lock(mtx);
cond_var.wait(lock, [&](){return done;});
// We now know the consumer has finished all updates.
// So we can simply loop over the goods and processes them
while (!goods.empty())
{
goods.pop();
c--;
}
});
I'm wanting to have several threads all waiting on a conditional variable (CV) and when the main thread updates a variable they all execute. However, I need the main thread to wait until all these have completed before moving on. The other threads don't end and simply go back around and wait again, so I can't use thread.join() for example.
I've got the first half working, I can trigger the threads, but the main just hangs and doesn't continue. Below is my current code
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <Windows.h>
#define N 3
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
bool finished[N];
void print_id(int id) {
while (1) {
std::unique_lock<std::mutex> lck(mtx); //Try and Lock the Mutex
while (finished[id]) cv.wait(lck); //Wait until finished is false
// ...
std::cout << "thread " << id << '\n';
finished[id] = true; //Set finished to be true. When true, program should continue
}
}
int main()
{
std::thread threads[N];
// spawn 10 threads:
for (int i = 0; i < N; ++i) {
threads[i] = std::thread(print_id, i); //Create n threads
finished[i] = true; //Set default finished to be true
}
std::cout << "N threads ready to race...\n";
for (int i = 0; i < 5; i++) {
std::unique_lock<std::mutex> lck(mtx); //Lock mutex
for (int i = 0; i < N; i++) {
finished[i] = false; //Set finished to false, this will break the CV in each thread
}
cv.notify_all(); //Notify all threads
cv.wait(lck, [] {return finished[0] == true; }); //Wait until all threads have finished (but not ended)
std::cout << "finished, Sleeping for 2s\n";
Sleep(2000);
}
return 0;
}
Thank you.
Edit: I am aware I am only currently checking the status of the finished[0] and not each one. This is done just for simplicity atm and would eventually need to be all of them. I will write a function to manage this later.
You have cv.wait(lck, [] {return finished[0] == true; }); in main thread, but it is not being notified.
You'd need to notify it, and you'd better use another condition_variable for it, not the same as for worker thead notifiecation.
I am trying to use condition variable to trigger a second thread to do some work after a certain number of buffers are in a deque.
main() is reading ADC data in a tight and time sensitive loop
main() adds the buffer to a deque (that is processed by a thread pool
manager)
the deque depth is variable so a test in main() for depth >= 4 and
then calling notify_one is not possible (ie: during heavy processing
times, the depth of the deque could be as much as 200 or 300 buffers
or as little as 4 during light processing)
thrd() never needs to signal main()
My problem is that in thrd(), after getting the signal and buffers < 4, it cycles back through the while loop and immediately gets the condition again. Is there a way to reset the cv?
deque<int> vbufs;
std::mutex thrdLock;
void thrd()
{
while (true) {
cout << "thrd() waiting for lock\n";
std::unique_lock < std::mutex > lock(thrdLock);
cout << "thrd() waiting for condition\n";
cv.wait(lock, []{ return (vbufs.size() > 0); });
thrdLock.unlock();
cout << "thrd() condition set\n";
if (vbufs.size() >= 4) { // pretend to do something with 4 buffers and remove them
std::lock_guard < std::mutex > lock(thrdLock);
vbufs.pop_front();
vbufs.pop_front();
vbufs.pop_front();
vbufs.pop_front();
cout << "thrd() reducing buffers:" << vbufs.size() << endl;
}
}
}
int main(int argc, char* argv[]) {
std::thread t1(thrd);
int tctr = 0;
while (true) {
usleep(1000);
{
cout << "main() waiting for lock\n";
std::lock_guard < std::mutex > lock(thrdLock);
vbufs.push_back(++tctr);
cout << "main() incremented buffers:" << vbufs.size() << endl;
}
cv.notify_one();
}
return 0;
}
You can't reset a condition variable; it makes no sense - notifying it doesn't change its state so there is nothing to reset. When it is notified only a thread already waiting can be awoken. If that thread then waits again it will not continue until the condition variable is re-notified.
If you only want to do work when there are four or more buffers, shouldn't you change your wait like this?
cv.wait(lock, []{ return (vbufs.size() >= 4); });
What's much more important is that you are potentially reading and writing vbufs simultaneously from two different threads - your if (vbufs.size() >= 4) occurs outside the lock, and so can happen at the same time as the calls to push_back. That leads to undefined behaviour, which may explain what you are seeing.
I found a couple of bugs in the program - corrected here:
Note: the way you were unlocking the mutex directly rather than through the std::unique_lock was causing a data race.
#include <iostream>
#include <deque>
#include <mutex>
#include <thread>
#include <chrono>
#include <condition_variable>
std::deque<int> vbufs;
std::mutex thrdLock;
std::condition_variable cv;
template<class...Ts>
void emit(Ts&&...ts)
{
static std::mutex m;
std::lock_guard<std::mutex> lg(m);
using expand = int[];
void(expand { 0, ((std::cout << std::forward<Ts>(ts)), 0)... });
}
void thrd()
{
while (true) {
emit("thrd() waiting for lock\n");
std::unique_lock < std::mutex > lock(thrdLock);
emit("thrd() waiting for condition\n");
cv.wait(lock, []{ return vbufs.size() >= 4; });
emit("thrd() condition set\n");
auto a = vbufs.front(); vbufs.pop_front();
auto b = vbufs.front(); vbufs.pop_front();
auto c = vbufs.front(); vbufs.pop_front();
auto d = vbufs.front(); vbufs.pop_front();
emit("thrd() reducing buffers:", vbufs.size(), '\n');
lock.unlock();
emit(a, ' ', b, ' ', c, ' ', d, '\n');
}
}
int main(int argc, char* argv[]) {
std::thread t1(thrd);
int tctr = 0;
while (true) {
std::this_thread::sleep_for(std::chrono::microseconds(10));
{
emit("main() waiting for lock\n");
std::lock_guard < std::mutex > lock(thrdLock);
vbufs.push_back(++tctr);
emit("main() incremented buffers:", vbufs.size(), '\n');
}
cv.notify_one();
}
t1.join(); // un-necessary in this case, but...
return 0;
}
I have an implementation of Semaphore to manage a shared resource using boost::threads. My implementation of the Semaphore is as shown below.
void releaseResource()
{
boost::unique_lock<boost::mutex> lock(mutex_);
boost::thread::id curr_thread = boost::this_thread::get_id();
// scan through to identify the current thread
for (unsigned int i=0; i<org_count;++i)
{
if (res_data[i].thread_id == curr_thread)
{
res_data[i].thread_id = boost::thread::id();
res_data[i].available = true;
break;
}
}
++count_;
condition_.notify_one();
}
unsigned int acquireResource()
{
boost::unique_lock<boost::mutex> lock(mutex_);
while(count_ == 0) { // put thread to sleep until resource becomes available
condition_.wait(lock);
}
--count_;
// Scan through the resource list and hand a index for the resource
unsigned int res_ctr;
for (unsigned int i=0; i<org_count;++i)
{
if (res_data[i].available)
{
res_data[i].thread_id = boost::this_thread::get_id();
res_data[i].available = false;
res_ctr = i;
break;
}
}
return res_ctr;
}
My question is regarding the performance degradation that I notice when there are more threads than the number of resources available. If I use notify_all to wake threads up instead of notify_one as shown in the releaseResource() code, I see that the performance improves. Has anyone else experienced something similar?
I am using Windows 7 and boost 1.52.