问题
I currently have two threads a producer and a consumer. The producer is a static methods that inserts data in a Deque type static container and informs the consumer through boost::condition_variable
that an object has been inserted in the deque object . The consumer then reads data from the Deque type and removes it from the container.The two threads communicate using boost::condition_variable
Here is an abstract of what is happening. This is the code for the consumer and producer
//Static Method : This is the producer. Different classes add data to the container using this method
void C::Add_Data(obj a)
{
try
{
int a = MyContainer.size();
UpdateTextBoxA("Current Size is " + a);
UpdateTextBoxB("Running");
MyContainer.push_back(a);
condition_consumer.notify_one(); //This condition is static member
UpdateTextBoxB("Stopped");
}
catch (std::exception& e)
{
std::string err = e.what();
}
}//end method
//Consumer Method - Runs in a separate independent thread
void C::Read_Data()
{
while(true)
{
boost::mutex::scoped_lock lock(mutex_c);
while(MyContainer.size()!=0)
{
try
{
obj a = MyContainer.front();
....
....
....
MyContainer.pop_front();
}
catch (std::exception& e)
{
std::string err = e.what();
}
}
condition_consumer.wait(lock);
}
}//end method
Now the objects being inserted in the Deque
type object are very fast about 500 objects a second.While running this I noticed that TextBoxB was always at "Stopped" while I believe it was suppose to toggle between "Running" and "Stoped". Plus very slow. Any suggestions on what I might have not considered and might be doing wrong ?
回答1:
1) You should do MyContainer.push_back(a);
under mutex - otherwise you would get data race, which is undefined behaviour (+ you may need to protect MyContainer.size();
by mutex too, depending on it's type and C++ISO/Compiler version you use).
2) void C::Read_Data()
should be:
void C::Read_Data()
{
scoped_lock slock(mutex_c);
while(true) // you may also need some exit condition/mechanism
{
condition_consumer.wait(slock,[&]{return !MyContainer.empty();});
// at this line MyContainer.empty()==false and slock is locked
// so you may pop value from deque
}
}
3) You are mixing logic of concurrent queue with logic of producing/consuming. Instead you may isolate concurrent queue part to stand-alone entity:
LIVE DEMO
// C++98
template<typename T>
class concurrent_queue
{
queue<T> q;
mutable mutex m;
mutable condition_variable c;
public:
void push(const T &t)
{
(lock_guard<mutex>(m)),
q.push(t),
c.notify_one();
}
void pop(T &result)
{
unique_lock<mutex> u(m);
while(q.empty())
c.wait(u);
result = q.front();
q.pop();
}
};
Thanks for your reply. Could you explain the second parameter in the conditional wait statement
[&]{return !MyContainer.empty();}
There is second version of condition_variable::wait which takes predicate as second paramter. It basically waits while that predicate is false, helping to "ignore" spurious wake-ups.
[&]{return !MyContainer.empty();}
- this is lambda function. It is new feature of C++11 - it allows to define functions "in-place". If you don't have C++11 then just make stand-alone predicate or use one-argument version of wait
with manual while loop:
while(MyContainer.empty()) condition_consumer.wait(lock);
One question in your 3rd point you suggested that I should Isolate the entire queue while My adding to the queue method is static and the consumer(queue reader) runs forever in a separate thread. Could you tell me why is that a flaw in my design?
There is no problem with "runs forever" or with static
. You can even make static concurrent_queue<T> member
- if your design requires that.
Flaw is that multithreaded synchronization is coupled with other kind of work. But when you have concurrent_queue - all synchronization is isolated inside that primitive, and code which produces/consumes data is not polluted with locks and waits:
concurrent_queue<int> c;
thread producer([&]
{
for(int i=0;i!=100;++i)
c.push(i);
});
thread consumer([&]
{
int x;
do{
c.pop(x);
std::cout << x << std::endl;
}while(x!=11);
});
producer.join();
consumer.join();
As you can see, there is no "manual" synchronization of push/pop
, and code is much cleaner.
Moreover, when you decouple your components in such way - you may test them in isolation. Also, they are becoming more reusable.
来源:https://stackoverflow.com/questions/15913142/communication-b-w-two-threads-over-a-common-datastructure-design-issue