第一次在Cnblog发文章,很不幸,在文章要完成居然出现了...Internet Exlpore执行了非法操作。.我靠.
起因:
我在实现Xxmm3(一个O/R)的Cache部份时需要一个可多线读写的hashtable。找了一下资料,木找到,如果各位有资料的话,给我提个醒,谢谢。
看了msdn的 Hashtable.Synchronized;
通过集合枚举在本质上不是一个线程安全的过程。甚至在对集合进行同步处理时,其他线程仍可以修改该集合,这会导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。
也就是说,我仍然无法实现多线程安全的读写(在枚举过程,仍然无法改写hashtable)
解决:
为了高效的实现目标,多线程安全的读写hashtable,需要解决三个问题
1。在开始一个或多个枚举时,禁止对hashtable的改写
2。在改写ht,但改写未结束时,不能再开始一个新的枚举。
3。允许多个枚举并发
为了解决1,在开始枚举时,需要设置一个标志m_CanWrite 禁止改写操作。
为了解决2,在改写ht的时,不能设置一个flag m_IsWrite从儿禁止/允许一个新枚举的开始
为了解决3,需要一个枚举计数器,知道最后一个枚举结束时,才能允许改写ht.
一个SafeHashtable类:
using
System;
using System.Collections;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// SafeHashtable 的摘要说明。
/// </summary>
public class SafeHashtable
{
private Hashtable ht = new Hashtable();
private volatile bool m_CanWrite = true ;
private volatile bool m_IsWrite = false ;
private volatile int m_EnumerateCount = 0 ;
public SafeHashtable()
{
}
private void SafeWait()
{
// 如果hastable不允许写
// 也就是一个枚举正在进行或另一个写如操作正在发生时
// 线程等待知道 m_CanWrite为true
while ( ! m_CanWrite)
{
Thread.Sleep( 10 );
}
}
public void Add( object key, object v)
{
// 当Add操作发生时但未结束时
// 要保证Remove操作不会不会将m_IsWrite写成true
// Remove操作也是一样的道理
this .SafeWait();
this .m_CanWrite = false ;
this .m_IsWrite = true ;
ht.Add(key,v);
this .m_IsWrite = false ;
this .m_CanWrite = true ;
}
public void Remove( object key)
{
this .SafeWait();
this .m_CanWrite = false ;
this .m_IsWrite = true ;
ht.Remove(key);
this .m_IsWrite = false ;
this .m_CanWrite = true ;
}
public Hashtable StartEnumerate()
{
// 等带写操作结束
while (m_IsWrite)
{
Thread.Sleep( 10 );
}
this .m_CanWrite = false ;
this .m_EnumerateCount ++ ;
Hashtable eht = Hashtable.Synchronized(ht);
return eht;
}
public void EndEnumerate()
{
this .m_EnumerateCount -- ;
// 禁止再开始一个枚举
this .m_IsWrite = true ;
// 如果这是最后一个枚举
// 则允许hashtable可写入
if ( this .m_EnumerateCount <= 0 )
{
this .m_CanWrite = true ;
}
// 允许再开始一个枚举
this .m_IsWrite = false ;
}
}
}
using System.Collections;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// SafeHashtable 的摘要说明。
/// </summary>
public class SafeHashtable
{
private Hashtable ht = new Hashtable();
private volatile bool m_CanWrite = true ;
private volatile bool m_IsWrite = false ;
private volatile int m_EnumerateCount = 0 ;
public SafeHashtable()
{
}
private void SafeWait()
{
// 如果hastable不允许写
// 也就是一个枚举正在进行或另一个写如操作正在发生时
// 线程等待知道 m_CanWrite为true
while ( ! m_CanWrite)
{
Thread.Sleep( 10 );
}
}
public void Add( object key, object v)
{
// 当Add操作发生时但未结束时
// 要保证Remove操作不会不会将m_IsWrite写成true
// Remove操作也是一样的道理
this .SafeWait();
this .m_CanWrite = false ;
this .m_IsWrite = true ;
ht.Add(key,v);
this .m_IsWrite = false ;
this .m_CanWrite = true ;
}
public void Remove( object key)
{
this .SafeWait();
this .m_CanWrite = false ;
this .m_IsWrite = true ;
ht.Remove(key);
this .m_IsWrite = false ;
this .m_CanWrite = true ;
}
public Hashtable StartEnumerate()
{
// 等带写操作结束
while (m_IsWrite)
{
Thread.Sleep( 10 );
}
this .m_CanWrite = false ;
this .m_EnumerateCount ++ ;
Hashtable eht = Hashtable.Synchronized(ht);
return eht;
}
public void EndEnumerate()
{
this .m_EnumerateCount -- ;
// 禁止再开始一个枚举
this .m_IsWrite = true ;
// 如果这是最后一个枚举
// 则允许hashtable可写入
if ( this .m_EnumerateCount <= 0 )
{
this .m_CanWrite = true ;
}
// 允许再开始一个枚举
this .m_IsWrite = false ;
}
}
}
当然你完全可以从让Safehashtable从hashtable继承而来,因为cache操作频繁,我不想使用override
为什么要使用:while(..){sleep()}
我测试了一下,如果不使用Sleep(...),而让while循环体为空的话,十分占用cpu,测试的时候达到了100%.还是Sleep要省一些资源。
需要开始一个枚举的时候,你需要调用 StartEnumerate()枚举结束时,要记得掉用:EndEnumerate这是一个成对操作.否则其他线程就无法再改写ht了。
下面是测试代码:
using
System;
using System.Collections;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
// static Hashtable ht = new Hashtable();
static SafeHashtable ht = new SafeHashtable();
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main( string [] args)
{
for ( int j = 1 ;j <= 3 ;j ++ )
{
for ( int i = 0 ;i < 10000 ;i ++ )
{
ht.Add(j * 1000000 + i,i * 100 );
}
ThreadStart myThreadDelegate = new ThreadStart(Class1.FreeSpac);
Thread myThread = new Thread(myThreadDelegate);
myThread.Name = " 000 " ;
myThread.Start();
myThread = new Thread(myThreadDelegate);
myThread.Name = " 999 " ;
myThread.Start();
Write(j);
}
Console.ReadLine();
}
static void FreeSpac()
{
Hashtable safeHt = ht.StartEnumerate();
// lock(typeof(Class1))
{
// ht.CanWrite = false;
foreach (DictionaryEntry entry in safeHt)
{
Console.WriteLine( " Thread[{2}] Key[{0}] Value[{1}] " ,entry.Key,entry.Value,Thread.CurrentThread.Name);
}
ht.EndEnumerate();
}
}
static void Write( int j)
{
Thread.Sleep( 100 );
for ( int i = 10000 ;i < 20000 ;i ++ )
{
ht.Add((i + j * 10000000 ).ToString() + " * " ,DateTime.Now);
}
}
}
}
using System.Collections;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
// static Hashtable ht = new Hashtable();
static SafeHashtable ht = new SafeHashtable();
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main( string [] args)
{
for ( int j = 1 ;j <= 3 ;j ++ )
{
for ( int i = 0 ;i < 10000 ;i ++ )
{
ht.Add(j * 1000000 + i,i * 100 );
}
ThreadStart myThreadDelegate = new ThreadStart(Class1.FreeSpac);
Thread myThread = new Thread(myThreadDelegate);
myThread.Name = " 000 " ;
myThread.Start();
myThread = new Thread(myThreadDelegate);
myThread.Name = " 999 " ;
myThread.Start();
Write(j);
}
Console.ReadLine();
}
static void FreeSpac()
{
Hashtable safeHt = ht.StartEnumerate();
// lock(typeof(Class1))
{
// ht.CanWrite = false;
foreach (DictionaryEntry entry in safeHt)
{
Console.WriteLine( " Thread[{2}] Key[{0}] Value[{1}] " ,entry.Key,entry.Value,Thread.CurrentThread.Name);
}
ht.EndEnumerate();
}
}
static void Write( int j)
{
Thread.Sleep( 100 );
for ( int i = 10000 ;i < 20000 ;i ++ )
{
ht.Add((i + j * 10000000 ).ToString() + " * " ,DateTime.Now);
}
}
}
}
如果文中有什么不当,欢迎拍拍.