Communication b/w two threads over a common datastructure. Design Issue

一曲冷凌霜 提交于 2019-12-10 17:28:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!