我刚开始使用线程, 我想写简单的文件压缩器。它应该创建两个后台线程 - 一个用于读取,另一个用于写入。第一个应该通过小块读取文件并将它们放入Queue,其中int - 是chunkId。第二个线程应该将块出列并按顺序将它们(使用chunkId)写入输出流(该线程在开始时创建的文件)。
我做到了。但我不明白为什么在我的程序结束后我打开我的gziped文件 - 我看,我的块混合了,文件没有先前的订单。
public static class Reader
{
private static readonly object Locker = new object();
private const int ChunkSize = 1024*1024;
private static readonly int MaxThreads;
private static readonly Queue<KeyValuePair<int, byte[]>> ChunksQueue;
private static int _chunksComplete;
static Reader()
{
MaxThreads = Environment.ProcessorCount;
ChunksQueue = new Queue<KeyValuePair<int,byte[]>>(MaxThreads);
}
public static void Read(string filename)
{
_chunksComplete = 0;
var tRead = new Thread(Reading) { IsBackground = true };
var tWrite = new Thread(Writing) { IsBackground = true };
tRead.Start(filename);
tWrite.Start(filename);
tRead.Join();
tWrite.Join();
Console.WriteLine("Finished");
}
private static void Writing(object threadContext)
{
var filename = (string) threadContext;
using (var s = File.Create(filename + ".gz"))
{
while (true)
{
var dataPair = DequeueSafe();
if (dataPair.Value == null)
return;
while (dataPair.Key != _chunksComplete)
{
Thread.Sleep(1);
}
Console.WriteLine("write chunk {0}", dataPair.Key);
using (var gz = new GZipStream(s, CompressionMode.Compress, true))
{
gz.Write(dataPair.Value, 0, dataPair.Value.Length);
}
_chunksComplete++;
}
}
}
private static void Reading(object threadContext)
{
var filename = (string) threadContext;
using (var s = File.OpenRead(filename))
{
var counter = 0;
var buffer = new byte[ChunkSize];
while (s.Read(buffer, 0, buffer.Length) != 0)
{
while (ChunksQueue.Count == MaxThreads)
{
Thread.Sleep(1);
}
Console.WriteLine("read chunk {0}", counter);
var dataPair = new KeyValuePair<int, byte[]>(counter, buffer);
EnqueueSafe(dataPair);
counter++;
}
EnqueueSafe(new KeyValuePair<int, byte[]>(0, null));
}
}
private static void EnqueueSafe(KeyValuePair<int, byte[]> dataPair)
{
lock (ChunksQueue)
{
ChunksQueue.Enqueue(dataPair);
}
}
private static KeyValuePair<int, byte[]> DequeueSafe()
{
while (true)
{
lock (ChunksQueue)
{
if (ChunksQueue.Count > 0)
{
return ChunksQueue.Dequeue();
}
}
Thread.Sleep(1);
}
}
}
UPD: 我只能使用.NET 3.5
答案 0 :(得分:3)
Stream.Read()
返回它消耗的实际字节数。用它来限制编写器的块大小。而且,由于涉及并发读写,因此您需要多个缓冲区。
尝试4096作为块大小。
阅读器:
var buffer = new byte[ChunkSize];
int bytesRead = s.Read(buffer, 0, buffer.Length);
while (bytesRead != 0)
{
...
var dataPair = new KeyValuePair<int, byte[]>(bytesRead, buffer);
buffer = new byte[ChunkSize];
bytesRead = s.Read(buffer, 0, buffer.Length);
}
编剧:
gz.Write(dataPair.Value, 0, dataPair.Key)
PS:通过添加一个空闲数据缓冲池而不是每次分配新的并使用事件(例如ManualResetEvent
)来发信号队列是空的,,可以提高性能>队列已满,而不是使用Thread.Sleep()
。
答案 1 :(得分:2)
尽管alexm's answer确实提出了一个非常重要的一点,Stream.Read
可以填充buffer
的字节数少于您请求的字节数,但您遇到的主要问题是您只有一个{ {1}}你一直在反复使用。
当你的阅读循环读取第二个值时,它会覆盖你传递给队列的byte[]
内的byte[]
。您必须在循环中使用dataPair
来解决此问题。您还必须记录读入的字节数,并且只写入相同的字节数。
您不需要将buffer = new byte[ChunkSize];
保留在对中counter
将保持订单,使用对中的Queue
来存储记录的字节数就像在alexm的例子中一样。