线程安全FIFO /队列(多个生产者,一个消费者)

时间:2016-03-09 01:31:04

标签: c++ multithreading queue fifo

正如标题所说,我正在尝试编写一个可由多个线程写入并由单个线程读取的队列。作为一个额外的困难,我需要队列输入保持有序(先进先出)。这就是我失去的地方。互斥锁不一定按它们被锁定的顺序被唤醒,所以我不知道我能用什么来实现我想要的东西?这是一个简单的程序,说明了我正在尝试做的事情:

#include "Queue.h"
#include <Windows.h>
#include <fstream>
#include <mutex>

using std::ofstream;

ofstream myFile("result.txt");
Queue<int> myQueue;

DWORD WINAPI WritingThread(LPVOID lpParam);
DWORD WINAPI LockingThread(LPVOID lpParam);

int main()
{
    // This thread will block myQueue for 3 seconds
    CreateThread(NULL, 0, LockingThread, NULL, 0, NULL);

    // During the locked period, I ask myQueue to push numbers from 0 to 49
    for (int i = 0; i < 50; i++)
        CreateThread(NULL, 0, WritingThread, (LPVOID)new int(i), 0, NULL);

    // If the mutex could wake up in order, myQueue would pop up the numbers in order, but it doesn't.
    for (int i = 0; i < 50; i++)
        myFile << myQueue.pop() << ",";

    return EXIT_SUCCESS;
}

DWORD WINAPI LockingThread( LPVOID lpParam ) 
{
    myQueue.lockQueueFor3Seconds();
    return 0;
}

DWORD WINAPI WritingThread( LPVOID lpParam ) 
{
    myQueue.push(*(int*)lpParam);
    return 0;
}

获取类Queue的代码there, see the bottom of the article for full code.我所做的只是添加方法“lockQueueFor3Seconds”以进行测试。该方法定义如下:

void lockQueueFor3Seconds()
{
    std::unique_lock<std::mutex> mlock(mutex_);
    Sleep(3000);
}

该测试的输出如下:

1,43,39,46,36,44,49,40,35,42,32,31,28,41,27,38,24,23,20,34,19,16,15,12,37,11,7,8,3,33,30,0,45,4,26,18,48,21,47,22,25,17,14,10,6,29,9,2,13,5

如你所见,显然没有订购。谢谢你的帮助!

编辑:我修改了队列,以便它为每个表示订单的推送调用一个数字,当互斥锁解锁时,队列检查以确保在添加元素之前它是正确的方法,否则它会返回到等候。不确定我是否正确实现了这一点,但似乎有效!完整的代码可以找到there

1 个答案:

答案 0 :(得分:2)

永远不会为线程分配要添加的值并期望按顺序添加它们,因为您不能强制线程执行的顺序。

相反,让每个线程在运行时添加下一个数字(无论它是什么)。像这样:

std::atomic_int counter;

DWORD WINAPI WritingThread( LPVOID lpParam ) 
{
    myQueue.push( counter++ );
    return 0;
}

编辑:增量不是原子的。递增和推送到队列需要是单个原子操作。这意味着将锁变量暴露在类之外(它已经公开)。

std::atomic_int counter;

DWORD WINAPI WritingThread( LPVOID lpParam ) 
{
    unique_lock<mutex> lock(myQueue.m_mutex);
    myQueue.push( counter++ );
    return 0;
}

如果您的mutex实现允许同一个线程多次调用它,那将会有效。否则,你可以做类似的事情:

void pushAndIncrement(T& item)
{
    std::unique_lock<std::mutex> mlock(mutex_);
    queue_.push(item);
            ++item;
    mlock.unlock();
    cond_.notify_one();
}

我认为您的解决方案(您说工作正常)仍然存在竞争条件。如果在增加字母值之后有一个上下文切换,但在它增加counter内的push值之前,它将以错误的顺序添加该字母。它是一个如此小的窗口,它可能不太可能发生,但是如果你把计数器增量放在同一个锁中,那么它每次都是完美的。