线程I / O重新排序缓冲区的标准术语?

时间:2010-05-28 21:46:05

标签: algorithm concurrency computer-science

我遇到一种情况,其中许多线程同时生成最终写入一个长的串行文件流的数据。我需要以某种方式序列化这些写入,以便以正确的顺序写入流。

ie ,我有一个2048个作业的输入队列j 0 .. j n ,每个作业产生一块数据o <子> I 。作业并行运行,例如,8个线程,但输出块必须以与相应输入块相同的顺序出现在流中 - 输出文件必须按o 0 的顺序排列0 <子> 1 0 <子> 2 ...

对此的解决方案非常明显:我需要某种缓冲区,以正确的顺序累积和写入输出块,类似于Tomasulo's algorithm中的CPU重新排序缓冲区,或者TCP重组的方式在将它们传递给应用程序层之前的无序数据包。

在我编写代码之前,我想做一个快速的文献检索,看看是否有任何论文以特别聪明或有效的方式解决了这个问题,因为我有严重的实时和内存限制。我似乎无法找到任何描述这个的论文;对[线程,并发,重新排序缓冲区,重组,io,序列化]的每个排列的学术搜索都没有产生任何有用的东西。我觉得我必须不是在寻找合适的条款。

我可以搜索这种模式的常用学术名称或关键字吗?

5 个答案:

答案 0 :(得分:1)

Enterprise Integration Patterns本书称之为 Resequencer (p282 / web)。

答案 1 :(得分:0)

实际上,您不需要累积块。大多数操作系统和语言都提供随机访问文件抽象,允许每个线程独立地将其输出数据写入文件中的正确位置,而不会影响来自任何其他线程的输出数据。

或者您是否正在写入真正的串行输出文件,如套接字?

答案 2 :(得分:0)

我个人根本不会使用可重新排序的缓冲区。我会为每个作业创建一个“作业”对象,并根据您的环境,使用消息传递或互斥来按顺序从每个作业接收已完成的数据。如果下一个工作没有完成,你的“作家”流程会一直等到它。

答案 3 :(得分:0)

我会使用与您使用的线程数相同长度的ringbuffer。环形缓冲区也具有相同数量的互斥锁。

rinbuffer还必须知道它写入文件的最后一个块的 id 。它相当于你的ringbuffer的0索引。

在添加到ringbuffer时,检查是否可以写入,即设置了索引0,然后可以一次向文件写入多个块。

如果未设置索引0,只需将当前线程锁定等待即可。 - 你也可以有一个长度比你的线程数长2-3倍的环形缓冲区,并且只在适当时锁定,即:当已经启动足够的工作来填满缓冲区时。

不要忘记更新最后写的硬块;)

写入文件时也可以使用双缓冲。

答案 4 :(得分:0)

让输出队列包含期货而不是实际数据。当您从输入队列中检索项目时,立即将相应的将来发布到输出队列中(注意确保这保留了订单 - 见下文)。当工作线程处理完项目后,它可以设置未来的值。输出线程可以从队列中读取每个未来,并阻塞直到该未来准备就绪。如果后期的早期就绪,这根本不会影响输出线程,只要期货是有序的。

有两种方法可以确保输出队列上的期货处于正确的顺序。第一种是使用单个互斥锁从输入队列中读取并写入输出队列。每个线程锁定互斥锁,从输入队列中获取一个项目,将未来发布到输出队列并释放互斥锁。

第二种方法是让一个主线程从输入队列中读取,在输出队列上发布未来,然后将该项目交给工作线程执行。

在C ++中使用单个互斥锁来保护队列,这看起来像是:

#include <thread>
#include <mutex>
#include <future>

struct work_data{};
struct result_data{};

std::mutex queue_mutex;
std::queue<work_data> input_queue;
std::queue<std::future<result_data> > output_queue;

result_data process(work_data const&); // do the actual work

void worker_thread()
{
    for(;;) // substitute an appropriate termination condition
    {
        std::promise<result_data> p;
        work_data data;
        {
            std::lock_guard<std::mutex> lk(queue_mutex);
            if(input_queue.empty())
            {
                continue;
            }
            data=input_queue.front();
            input_queue.pop();
            std::promise<result_data> item_promise;
            output_queue.push(item_promise.get_future());
            p=std::move(item_promise);
        }
        p.set_value(process(data));
    }
}

void write(result_data const&); // write the result to the output stream

void output_thread()
{
    for(;;) // or whatever termination condition
    {
        std::future<result_data> f;
        {
            std::lock_guard<std::mutex> lk(queue_mutex);
            if(output_queue.empty())
            {
                continue;
            }
            f=std::move(output_queue.front());
            output_queue.pop();
        }
        write(f.get());
    }
}