以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 Dot NET,C#,ASP,VB 』 (http://bbs.xml.org.cn/list.asp?boardid=43) ---- 多线程:从对象池(object pool)谈同步(syncronization) ——一个调试的问题 (http://bbs.xml.org.cn/dispbbs.asp?boardid=43&rootid=&id=76602) |
-- 作者:卷积内核 -- 发布时间:9/5/2009 9:06:00 AM -- 多线程:从对象池(object pool)谈同步(syncronization) ——一个调试的问题 最近,刚看过Jeffry Richter的《Programming Application for Microsoft Windows 4th Edition》。眼下正看《C# Threading HandBook》。看了前三章,觉得很不错。觉得这本书很系统,自己也想把以前在一些书上看到的分散的东西彻底归整一遍。于是就从这里开个头吧。 还记得Jeffry Richter在《Appiled .NET Framework Programming》里的那个利用对象复苏设计的那个对象池吗? 且请容许我把代码在这里再贴一遍: 1using System; 但是,这个实现很大的限制了我们的应用。 我们被限制的应用有哪些呢? 这个object pool还不够智能化,每次我们在开始运行时,要自己手动构造一些对象,在应用程序退出时,还必须牢记要自己关闭object pool。 在这里我要对HashTable多说两句。《Professinal C# 3rd Edition》里说的很清楚: 容量为素数的话,工作效率更高,且当散列表扩大容量重新分配内存的时候,总会选择一个素数作为其新的容量。 对于System.Object来说,Equals()仅仅比较引用,而GetHashCode()会根据对象的地址返回一个散列。因此如果你的类这两个方法都不重载,将其运用到HashTable是可以正常工作的,但这样的类会受到“同一与相等”这个典型问题的限制。因此,最后自己为要用做键的类重写这两个方法。 此外,MS已经为String提供了一种虽然复杂、但很有效的散列算法。我们可以在自己的实现中利用这个算法。 最后,一般简单高效率的散列算法的设计是:获取字段,把它们与较大的素数相乘,再把结果加起来。 第二,既然我们选择了HashTable,那么用什么做主键,什么做值呢? 还记得,我们提过想把生命周期管理拿进来,而且希望对象的创建和销毁更自动化。鉴于此二者。我们可以把对象本身用做主键,而值用创建该对象时的时间来填充,每当被使用后,该值即立刻被更新。通过一个定时触发器根据对象的最近使用时间。来管理object pool中的对象。 第三,也就是关于对象的使用问题。其实,本质上讲,是一个有状态和无状态的问题。如果对象是有状态的,当我们从object pool取出一个对象后,该对象的状态不一定符合我们使用的要求。比如一个数据库连接,当我们从object pool取出时,它很有可能是关闭的。因此,这就很有必要在我们的取出操作中进行对象状态的验证。 第四,同步。除了谈到的使用线程安全的HashTable外,我们还有一些操作是需要原子特性的。我们可以把lock或者monitor施加在critical section上来得到保证。 说了这么多,我们来看看《C# Threading Handbook》中的这个更具使用价值的object pool的实现:
|
-- 作者:卷积内核 -- 发布时间:9/5/2009 9:07:00 AM -- 作为该书中的一个完整的例子。书中提供了一个数据库连接object pool的实现。代码如下: using System; using System.Data.SqlClient; namespace WroxCS { public sealed class DBConnectionSingleton : ObjectPool { private DBConnectionSingleton() {}
public static readonly DBConnectionSingleton Instance = new DBConnectionSingleton();
private static string _connectionString = @"server=(local); Integrated Security=SSPI;database=northwind";
public static string ConnectionString { set { _connectionString = value; } get { return _connectionString; } }
protected override object Create() { SqlConnection temp = new SqlConnection( ConnectionString); temp.Open(); return(temp); }
protected override bool Validate(object o) { try { SqlConnection temp = (SqlConnection)o; return( ! ((temp.State.Equals(System.Data.ConnectionState.Closed)))); } catch (SqlException) { return false; } }
protected override void Expire(object o) { try { ((SqlConnection) o ).Close(); } catch (SqlException) { } }
public SqlConnection BorrowDBConnection() { try { return((SqlConnection)base.GetObjectFromPool()); } catch (Exception e) { throw e; } }
public void ReturnDBConnection(SqlConnection c) { base.ReturnObjectToPool(c); } } } // Initialize the Pool DBConnectionSingleton pool; pool = DBConnectionSingleton.Instance; // Set the ConnectionString of the DatabaseConnectionPool DBConnectionSingleton.ConnectionString = "server=(local);User ID=sa;Password=;database=northwind"; // Borrow the SqlConnection object from the pool SqlConnection myConnection = pool.BorrowDBConnection(); // Return the Connection to the pool after using it pool.ReturnDBConnection(myConnection);
|
-- 作者:卷积内核 -- 发布时间:9/5/2009 9:07:00 AM -- 我添加的代码如下: private DBConnectionSingleton pool = DBConnectionSingleton.Instance; private void DBOperation() { // Borrow the SqlConnection object from the pool SqlConnection myConnection = pool.BorrowDBConnection(); SqlCommand catCMD = myConnection.CreateCommand(); catCMD.CommandText = "SELECT CategoryID,CategoryName FROM Categories"; SqlDataReader myReader = catCMD.ExecuteReader(); string[] args = new string[2]; UpdateData UIDel = new UpdateData(UpdateUI); while (myReader.Read()) { args[0] = myReader.GetInt32(0).ToString(); args[1]= myReader.GetString(1); //this.BeginInvoke(UIDel, args); UpdateUI(args[0],args[1]); // Return the Connection to the pool after using it pool.ReturnDBConnection(myConnection); } private void Connect_Click(object sender, System.EventArgs e) { //DBOperationThread = new Thread(new ThreadStart(this.DBOperation)); //DBOperationThread.Start(); this.DBOperation(); } 结果出现了令人费解的情况。每当pool.ReturnDBConnection(myConnection) 返回时,unlock便从1变成了0——一个完全毫无道理的事情。 而且当我检查代码覆盖情况时,发现Validate()方法从未执行过,准确的讲是每次从object pool中获取连接时,它总是创建新的对象——因为unlock为空。 而且,在这种情况下内存的使用随着每次创建新的连接而激增! 但如果我不单步执行,那么就不会存在上面的问题。从代码覆盖上就可以看到unlock曾经不是空的。
|
-- 作者:卷积内核 -- 发布时间:9/5/2009 9:08:00 AM -- Hit Line Source Code 1 using System; 2 using System.Collections; 3 using System.Timers; 4 5 namespace WroxCS 6 { 7 8 public abstract class ObjectPool 9 { 10 //Last Checkout time of any object from the pool. 11 private long lastCheckOut; 12 13 //Hashtable of the checked-out objects 14 private static Hashtable locked; 15 16 //Hashtable of available objects 17 private static Hashtable unlocked; 18 19 //Clean-Up interval 20 internal static long GARBAGE_INTERVAL = 5 * 1000; // 90 seconds 21 static ObjectPool() 22 { 23 locked = Hashtable.Synchronized(new Hashtable()); 24 unlocked = Hashtable.Synchronized(new Hashtable()); 25 } 26 27 internal ObjectPool() 28 { 29 lastCheckOut = DateTime.Now.Ticks; 30 31 //Create a Time to track the expired objects for cleanup. 32 System.Timers.Timer aTimer = new System.Timers.Timer(); 33 aTimer.Enabled = true; 34 aTimer.Interval = GARBAGE_INTERVAL; 35 aTimer.Elapsed += new 36 System.Timers.ElapsedEventHandler(CollectGarbage); 37 } 38 39 protected abstract object Create(); 40 41 protected abstract bool Validate(object o); 42 43 protected abstract void Expire(object o); 44 45 internal object GetObjectFromPool() 46 { 47 long now = DateTime.Now.Ticks; 48 lastCheckOut = now; 49 object o = null; 50 51 lock(this) 52 { 53 try 54 { 55 foreach (DictionaryEntry myEntry in unlocked) 56 { 57 o = myEntry.Key; 58 if (Validate(o)) 59 { 60 unlocked.Remove(o); 61 locked.Add(o, now); 62 return(o); 63 } 64 else 65 { 66 unlocked.Remove(o); 67 Expire(o); 68 o = null; 69 } 70 } 71 } catch (Exception){} 72 o = Create(); 73 locked.Add(o, now); 74 } 75 return(o); 76 } 77 78 internal void ReturnObjectToPool(object o) 79 { 80 if (o != null) 81 { 82 lock(this) 83 { 84 locked.Remove(o); 85 unlocked.Add(o, DateTime.Now.Ticks); 86 } 87 } 88 } 89 90 private void CollectGarbage(object sender, 91 System.Timers.ElapsedEventArgs ea) 92 { 93 lock(this) 94 { 95 object o; 96 long now = DateTime.Now.Ticks; 97 IDictionaryEnumerator e = unlocked.GetEnumerator(); 98 99 try 100 { 101 while(e.MoveNext()) 102 { 103 o = e.Key; 104 105 if ((now - ((long) unlocked[ o ])) > GARBAGE_INTERVAL ) 106 { 107 unlocked.Remove(o); 108 Expire(o); 109 o = null; 110 } 111 } 112 } 113 catch (Exception){} 114 } 115 } 116 } 117 } 而且从内存的使用情况,我还发现: 当应用程序运行一段时间后,每当我再次点击“连接”时,系统都会自动执行一次垃圾收集。设置断点后,再执行,则又会回到上面的情况。 问题出在哪里?还在思考中…… |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
109.375ms |