最近在多个线程中使用LIST集合作为数据交换容器, 类似于生产者消费者这样的模式。生产者使用List.Add() 方法增加元素,消费者使用List.RemoveAt(0)消费。咋一看,没什么问题。但程序越跑越大,虽然C#有垃圾回收机制,但是究其原因,实际是忽略了List.Add是值引用这个本质。另外, 我在这个例子中,使用Lock(o)来同步生产者消费者的行为是不可行的,原因很简单:Lock没有先后关系,谁有机会谁就获得了使用权。因而我用了Semaphore作为线程同步的媒介。
看代码吧。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication3
{
class Program
{
static Semaphore sm = new Semaphore(0, 5);
static void Main(string[] args)
{
List<string> list = new List<string>();
Thread t = new Thread(new ParameterizedThreadStart(Add));
t.Start(list);
Thread t2 = new Thread(new ParameterizedThreadStart(Minus));
t2.Start(list);
t2.Join();
Console.WriteLine("Length of List is {0}",list.Count);
Console.Read();
}
static void Add(object o)
{
List<string> list = o as List<string>;
int i = 0;
while (i<10000)
{
string ss = string.Format("samplesample.{0}", i.ToString());
list.Add(ss);
sm.Release();
Thread.Sleep(10); // 等10ms,这样不会超过semophore最大数量
Console.WriteLine("ADD {0}", i++);
}
}
static void Minus(object o)
{
List<string> list = o as List<string>;
int i = 0;
while (i < 10000)
{
sm.WaitOne();
list.RemoveAt(0);
Console.WriteLine("Minus {0}", i++);
}
}
}
}
最后的输出是:
没加即时垃圾回收,最后程序消耗内存 (可见,List.RemoveAt 也不是真正的删除string变量开销。)
而加上即时垃圾回收(上代码RemoveAt(0)后面加上GC.Collect())