以前多线程使用Hashtable的时候,都是使用lock(Hashtable)的方式进行同步,那天在网上忽然查到一个资料,就是Hashtable 对于多线程是安全的,可以实现自同步,也就是不需要lock也能实现同步,于是写了段代码测试:
private Hashtable mObjectHash = null;//定义一个哈希表
private int mMaxCount = 50000;//定义最大对象数
///定义的测试对象类
private class mShowObject
{
public int ID=-1;
public String Name;
public override string ToString()
{ return String.Format("{0}\t{1}", this.ID, this.Name); } } ///定义的一些变量,用于计数 long threadAccess = 0; int deleteObjectcount = 0;
int addobjectCount = 0;
int editobjectcount = 0;
DateTime startTime = DateTime.Now;//启动时间
bool mActive = false;
int mChangeThreadCount = 0;
Random rnd = null;
//构造函数
public Form1()
{
InitializeComponent();
mObjectHash = Hashtable.Synchronized(new Hashtable(mMaxCount));//初始化一个具有自同步属性的哈希表
rnd = new Random((int)DateTime.Now.Ticks);
}
private void AddObject(mShowObject obj)
{
if (obj == null) return;
lock (mObjectHash)
{
%%%% if (mObjectHash.ContainsKey(obj.ID)) return;//此处因为判断和添加事务之间可能会出现不同的结果,因此,需要手动同步
mObjectHash.Add(obj.ID, obj);
}
}
private void EditObject(mShowObject obj)
{
if (obj == null) return;
if (mObjectHash.ContainsKey(obj.ID)) {
mObjectHash[obj.ID] = obj; }
else mObjectHash.Add(obj.ID, obj);
}
private void RemoveObject(int id)
{
if (mObjectHash.ContainsKey(id))
mObjectHash.Remove(id); }
private mShowObject GetObject(int key)
{
if (mObjectHash.ContainsKey(key))
return (mShowObject)mObjectHash[key];
return null; }
//一个访问哈希表的线程
private void ChangeThread()
{
startTime = DateTime.Now;//启动时间
while (mActive)
{
Interlocked.Increment(ref threadAccess);
Thread.Sleep(0);
Random rnd1 = new Random();
int key = rnd1.Next(this.mMaxCount);
int Command = rnd1.Next(Int32.MaxValue);
if ((Command % 3) == 1)
{
mShowObject obj = GetObject(key);
if (obj != null)
{
obj.Name = String.Format("对象{0},修改于{1} #$", rnd.Next(mMaxCount), DateTime.Now.ToString("HH:mm:ss")); EditObject(obj);
Interlocked.Increment(ref editobjectcount); } }
else if ((Command % 3) == 2)
{
RemoveObject(key);
Interlocked.Increment(ref deleteObjectcount); }
else if ((Command % 3) == 0)
{
mShowObject obj = new mShowObject();
obj.ID = this.mObjectHash.Count;
obj.Name = String.Format("对象{0},创建于{1}", rnd.Next(mMaxCount), DateTime.Now.ToString("HH:mm:ss")); AddObject(obj);
Interlocked.Increment(ref addobjectCount);
}
}
}
//初始化对象
private void button1_Click(object sender, EventArgs e) {
for (int i = 0; i < mMaxCount; i++)
{
mShowObject obj = new mShowObject();
obj.ID = i;
obj.Name = String.Format("对象{0},创建于{1}", rnd.Next(mMaxCount), DateTime.Now.ToString("HH:mm:ss"));
AddObject(obj);
}
mActive = true;
}
//启动线程
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i < 400; i++)//启动400个线程
{
Thread t = new Thread(new ThreadStart(this.ChangeThread)); t.IsBackground = true;
t.Start();
Interlocked.Increment(ref mChangeThreadCount);
}
}
//使用定时器显示线程执行状态
private void timer1_Tick(object sender, EventArgs e)
{
TimeSpan ts = DateTime.Now - startTime;
float speed = threadAccess / (float)ts.TotalSeconds; this.label1.Text = String.Format("修改线程数量:{0:n0},哈希表对象数量:{1:n0},线程执行次数:{2:n0},删除对象数:{3:n0},修改对象数:{4:n0},增加对象数:{5:n0},速度:{6:n0}/s", mChangeThreadCount, this.mObjectHash.Count, threadAccess, deleteObjectcount, editobjectcount, addobjectCount, speed); }
下面是运行截图:
我的电脑是AMD闪龙2500+,512M内存,开400个线程的情况下大概每秒执行4万次访问,没有出现错误;而加了"%%%% "的那一行如果没有手工进行同步,就会出现问题,但是修改函数删除函数中没有进行手工同步却没有影响,估计是往哈希表添加项的时候需要做更多的事情,造成线程阻塞,多线程访问时候就出现了添加重复键的问题,而删除和编辑因为哈希表的索引很快,线程几乎没有阻塞,所以没有出错吧!
使用自同步在这里的确能提高一些性能,但是并不明显,因为对哈希表的操作都要执行2个步骤:先确定是否有指定的键,然后再决定是否添加删除或者修改,但凡要执行2步以上才能完成的操作,仅仅依靠对象自身的同步是绝对不安全的,因此,这里只讨论多线程下的安全操作,而不是讨论性能,性能的优化应该是尽可能的压缩同步范围,在同步期间少做与哈希表对象无关的操作,并且避免死锁.
.Net下,实现自同步的对象还有ArrayList,Queue等,经测试,最能体现性能的就是Queue,因为它只有一种操作:编辑(压入/弹出),因此在避开过多无谓的同步代码后,性能提高十分明显.
评论