如何在多线程中对数据进行分组记录?
如何在多线程中对数据进行分组记录?
我有两个方法。一个方法包含线程池,线程池运行第二个方法。
以下是代码片段:
static void Main() { var processes = GetProcessCounter(); foreach (var process in processes) { var tmp = process; ThreadPool.QueueUserWorkItem(WriteCounter, tmp); Console.Write("\n\n"); } Console.WriteLine("按任意键退出..."); Console.Read(); } static void WriteCounter(object obj) { var process = (ProcessInstance) obj; Console.WriteLine( $"线程ID: {Thread.CurrentThread.ManagedThreadId}, " + $"进程名称: {process.ProcessName}, " + $"进程ID: {process.ProcessId} "); Console.Write("\n"); foreach (var cnt in process.Counters) { cnt.NextValue(); Console.WriteLine( $"线程ID: {Thread.CurrentThread.ManagedThreadId} | " + $"分组: {cnt.CategoryName} | " + $"进程: {cnt.InstanceName} | " + $"名称: {cnt.CounterName} | " + $"数值: {cnt.GetCalculatedValue(cnt.CounterName)}"); cnt.Close(); } }
我看到它的运行正常。当我计算子循环时,它会在一个线程中与父循环一起运行。但在控制台中,我看到混乱的输出。
我应该如何按线程ID分组输出?
在多线程中进行数据记录时,可能会遇到以下问题和解决方法:
问题:如何对数据记录进行分组?
原因:在多线程环境下,多个线程同时进行数据记录可能会导致输出的数据混乱或者丢失。
解决方法:
1. 使用Console.WriteLine
时,每个线程只调用一次Console.WriteLine
。Console.WriteLine
是线程安全的,因此可以通过在线程中构建输出字符串,并在最后调用Console.WriteLine
来解决该问题。
2. 在所有Console.WriteLine
调用上显式使用一些线程同步机制,例如lock (consoleLock) { Console.WriteLine(...); for (...) Console.WriteLine(...)}
。但是,像前面提到的一次构建整个输出字符串那样,可以一次性锁定,这是一个更好的方法。在使用锁时,应该锁定尽可能短的时间间隔,因为如果将整个线程启动委托放在锁中,与单线程解决方案相比,它不会快多少,而且由于很多线程池线程在等待共享资源,从性能角度来看可能是不明智甚至有害的。
3. 如果想要进行更高级的操作,可以尝试使用一些consumer-producer
的实现(例如在控制台应用程序中同步不同线程的事件),其中只有一个线程从其他线程中写入消息。这与选项1和2没有太大区别,因为仍然是WriteCounter
的责任仅发送一次数据。
4. 如果希望更进一步地进行过度设计,可以将consumer-producer
与每个线程的缓冲区结合使用。可以通过以下方式定义一个public interface IBatchTextWriter
:
public interface IBatchTextWriter { TextWriter TextWriter { get; } ////// Ends the batch (everything written to the TextWriter will be written to the actual stream). void EndBatch(); }
并且可以像下面这样使用它:
var process = (ProcessInstance) obj; batchWriter.TextWriter.WriteLine( $"Thread ID: {Thread.CurrentThread.ManagedThreadId}, " + $"Process Name: {process.ProcessName}, " + $"Process ID {process.ProcessId} "); batchWriter.TextWriter.Write("\n"); foreach (var cnt in process.Counters) { cnt.NextValue(); batchWriter.TextWriter.WriteLine( $"ThreadId: {Thread.CurrentThread.ManagedThreadId} | " + $"Group: {cnt.CategoryName} | " + $"Process: {cnt.InstanceName} | " + $"Name: {cnt.CounterName} | " + $"Value: {cnt.GetCalculatedValue(cnt.CounterName)}"); cnt.Close(); } batchWriter.EndBatch();
附注:推荐阅读关于线程池和任务的文章-C# - ThreadPool vs Tasks。