设置线程数量作为消费者

时间:2017-03-15 23:56:05

标签: c++ multithreading mutex

我创建了一个生产者/消费者代码,如下所示

class CTest{
public:
    void producer( int i ){
        unique_lock<mutex> l(m);
        q.push(i);
        if( q.size() )
            cnd.notify_all();
    }

    void consumer(int i ){
        unique_lock<mutex> l(m);    
            while( q.empty() ){
                    cnd.wait(l );
                }
            if( q.empty())
                return;
            cout << "IM AWAKE :" << i  << endl;
            int tmp = q.front();
            q.pop();
            l.unlock();
            cout << "Producer got " << tmp << endl;
        }



    void ConsumerInit( int threads ){
        for( int i = 0; i < threads; i++ ){
            thrs.push_back(thread(&CTest::consumer, this ,i));
        }

    }
    void waitForTHreads(){
        for( auto &a : thrs )
            a.join();
    }
    void printQueue(){
        while( ! q.empty()){
            int tmp = q.front();
            q.pop();
            cout << "Queue got " << tmp << endl;
        }
    }
private:
    queue<int> q;
    vector<thread> thrs;
    mutex m;
    condition_variable cnd;
};

和主要

int main(){
    int x;   
    CTest t;
    int counter = 0;
    while( cin >> x ){
        if( x == 0 ){
            cout << "yay" << endl;;
            break;
        }
        if( x == 1)
            t.producer(counter++);
        if( x == 2 )
            t.ConsumerInit(5);
    }   
    t.waitForTHreads();
    t.printQueue();
    return 0;
}

当用户输入&#34; 1&#34;当用户输入&#34; 2&#34;它会在队列中添加数字。 ,产生5个线程以从队列中检索数据并打印它。然而,当我输入时,我的问题是如下 6个数字,其中只有5个被打印,因为事实上只产生了5个线程,我想做的是线程从队列中检索数据,打印int,然后再次等待它是否可以打印另一个数据。这样所有N>只有5个线程打印出5个数字。

我的问题是,如何实现这一目标的标准方法是什么?我读了很少的文件,但没有fint /想不出好的解决方案。这样的问题怎么解决了?

当我尝试创建简单的线程池时:

void consumer(int i ){
    while(true){
        {
            unique_lock<mutex> l(m);    
            while( q.empty() ){
                    cnd.wait(l );
                }
            if( q.empty())
                return;
            cout << "IM AWAKE :" << i  << endl;
            int tmp = q.front();
            q.pop();

            cout << "Producer " << i << " got " << tmp << endl;
        }   //consumer(i);
    }
}

并输入N数字所有数字都由一个线程处理。 谢谢你的帮助!

2 个答案:

答案 0 :(得分:0)

当前版本的consumer只能在退出前读取一个值。为了阅读更多内容,它必须循​​环播放,这会导致您的第二版consumer出现两个问题:

  1. 此处的消耗非常快,以至于进入队列的第一个线程可以在其时间片内消耗整个队列(或者正在分配CPU)。插入yield或sleep以强制操作系统切换任务。
  2. 互斥锁未解锁,因此没有其他线程可以进入。
  3. 幸运的是,在您需要它们之前,您不会创建线程,并且在队列为空后它们会终止,因此conditional_variable的整个处理可能会消失。

    void consumer(int i)
    {
        unique_lock<mutex> l(m);
        while (!q.empty())
        {
            int tmp = q.front();
            q.pop();
            cout << i << " got " << tmp << endl;
            // note: In the real world, locking around a cout is gross. cout is slow, 
            // so you want the unlock up one line. But...! This allows multiple threads
            // to write to the consle at the same time and that makes your output 
            // look like it was tossed into a blender, so we'll take the performance hit
            l.unlock(); // let other threads have a turn
            this_thread::yield();
            l.lock(); // lock again so the queue can be safely inspected
        }
    }
    

    如果你需要使用线程池方法,事情变得有点混乱,条件变量会返回。

    void consumer(int i)
    {
        while (true)
        {
            unique_lock<mutex> l(m);
            if (q.empty())
            {
                cnd.wait(l);
            }
            if (!q.empty()) // OK. We got out of the conditional wait, but have 
                            // other threads sucked the queue dry? Better check.
            {
                int tmp = q.front();
                q.pop();
                cout << i << " got " << tmp << endl;
            }
            l.unlock();
            this_thread::yield();
        }
    }
    

    atomic<bool> terminated可能有助于允许有序关机while (true)不允许。

答案 1 :(得分:0)

通常,在不进入代码细节的情况下,创建线程池并将线程置于等待状态(等待一个或多个事件/信号,或者在您的情况下condition_variable cnd;) - I&#39; m曾经处理过事件,所以我将在下面的文本中使用它,但condition_variable应该以类似的方式工作。

将任务添加到队列时,会设置/触发任务事件,并唤醒一个或多个线程(取决于事件(单个/多个))。

当线程被唤醒时,它会检查(带锁)是否有任务可用,如果可用,则执行任务,如果有更多任务等待,则再次完成检查(!)。 (因为当你一次添加8个任务时,5个线程变为活动状态,因此他们需要在完成第一个任务后检查是否有更多任务。

如果没有剩余作业,则线程将返回等待状态(等待下一个作业或退出事件)。

当退出应用程序时,为所有线程设置了另一个,比如quit-event(你不能等待线程完成,因为线程本身正在等待一个事件来做一些工作) - - 或者您可以触发相同的事件,并首先设置volatile variable,然后线程应首先检查任何事件以查看是否需要退出或执行其他工作。然后你可以等待线程回家了。

锁应尽可能短。

至于你的代码:

void producer( int i ){
    unique_lock<mutex> l(m);
    q.push(i);
    if( q.size() )
        cnd.notify_all();
}

此处锁定的持续时间超过了所需的时间(也可能太长)。你也只是推了一个值,所以q不会是空的(不需要检查)。由于您只添加了一个项目(任务),因此只应唤醒一个线程(因此notify_one()应该没问题。)

所以你应该:lock, push, unlock, notify - 而不是unlock,你可以将lockpush置于括号内,这将触发unlock unique_lock<>析构函数。

void consumer(int i ){
    unique_lock<mutex> l(m);    
        while( q.empty() ){
                cnd.wait(l );
            }
        if( q.empty())
            return;
        cout << "IM AWAKE :" << i  << endl;
        int tmp = q.front();
        q.pop();
        l.unlock();
        cout << "Producer got " << tmp << endl;
}

在这里你应lock, check queue, pop if there is a task, unlock,如果没有任务,再将线程置于等待状态,否则使用弹出值(解锁后),然后再次检查是否还有更多工作要做。通常情况下,在数据被锁定时调用cout不是一个好主意..但是对于一个小测试你可以逃脱它,特别是因为cout也需要同步(但它会是清理器自己同步cout,与数据锁分开。)

void printQueue(){
    while( ! q.empty()){
        int tmp = q.front();
        q.pop();
        cout << "Queue got " << tmp << endl;
    }
}

确保您的数据也在这里锁定! (虽然它只在线程完成后才从main调用,但是函数在你的类中,并且数据应该被锁定)。