[static]Static Function 的小實驗
static field 在程式撰寫時是要注意資料的同步性是無庸置疑,
但static function呢? 做一些實驗來確認自己觀念是否正確, 並做筆記.
1. 若一thread在引用function尚未結束, 另一個thread又引用方法時, 參數或區域變數會被蓋掉嗎?
A: 不會,不同的Thread在呼叫static function時,皆為獨立,不相互干擾,除非參數是共用的。
2. static function 是 thread-safe 嗎?
A: 不是,在value type 如:int, double ... 或 immutable 如:string,是無法被外部的thread異動,但物件可以。
參考:Are static methods Thread-Safe?
<< 以下實驗: 主thread 1條, 引用static function thread 2 條, wait 時間0.5秒. >>
Test Case 大致是長這樣:
List<Thread> pool = new List<Thread>(2);
Class2 parms = new Class2();
Thread t1 = new Thread(() =>
{
object actual;
actual = Class1.RunObjectLock3(parms);
Assert.AreEqual(new Class2() { x=45 }, actual);
});
pool.Add(t1);
t1.Start();
Thread t2 = new Thread(() =>
{
object actual;
actual = Class1.RunObjectLock3(parms);
Assert.AreEqual(new Class2() { x = 90 }, actual);
});
pool.Add(t2);
t2.Start();
WaitForQueue(pool);
3. 實驗 1: value type
public static int RunIntUnLock(int x)
{
for (int i = 0; i < 10; i++)
{
x += i;
Thread.CurrentThread.Join(wait);
}
return x;
}
A: 預計結果回傳45,測試結果成功。
4. 實驗 2: immutable
public static string RunStringUnLock(string s){
for (int i = 0; i < 10; i++)
{
s+=i.ToString();
Thread.CurrentThread.Join(wait);
}
return s;
}
A: 預計結果回傳"0123456789",測試結果成功。
5. 實驗 3: 物件-每次都new 一個instatnce
public static Class2 RunObjectUnLock(Class2 c2)
{
for (int i = 0; i < 10; i++)
{
c2.x += i;
Thread.CurrentThread.Join(wait);
}
return c2;
}
public class Class2
{
public int x = 0;
public override string ToString()
{
return x.ToString();
}
public override int GetHashCode()
{
return x;
}
public override bool Equals(object obj)
{
Class2 c2 = obj as Class2;
return c2==null ? false: x==c2.x;
}
}
A: 預計結果回傳45,測試結果成功。
6. 實驗 3: 物件-僅new一個共用物件
A: 預計結果回傳 (1) 45 (2) 90 , 測試結果失敗. 回傳介於45~90間的值。(Thread-Unsafe)
7. 實驗 4: 鎖定參數物件
public static Class2 RunObjectLock(Class2 c2)
{
lock (c2)
{
for (int i = 0; i < 10; i++)
{
c2.x += i;
Thread.CurrentThread.Join(wait);
}
return c2;
}
}
A: 預計結果回傳 (1) 45 (2) 90 , 測試結果失敗. 回傳介於45~90間的值。
8. 實驗5: 鎖定類別
public static Class2 RunObjectLock3(Class2 c2)
{
lock (typeof(Class2))
{
for (int i = 0; i < 10; i++)
{
c2.x += i;
Thread.CurrentThread.Join(wait);
}
return c2;
}
}
A: 預計結果回傳 (1) 45 (2) 90 , 測試結果成功。
<< 實驗結束 >>
<< 番外篇 >>
9. Visual Studio Unit Test 測試很快結束, 測試結果皆正確?!
A: 當主要thread走完時, 測試就會結束, 尚未走到Assert, 導致測試未完全, 結果卻正確.
主要thread增加等待機制, 如下:
private void WaitForQueue(List<Thread> pool)
{
bool finished = false;
do
{
var v = from _v in pool
where _v.IsAlive == true
select _v;
finished = (v.Count() == 0);
Thread.CurrentThread.Join(6000);
} while (!finished);
pool.Clear();
}
如果這段程式要寫的美一點,請參考黑暗大:匿名方法 vs 具名方法 的小小效能實驗使用Interlocked.Decrement
<< thinking in static function >>
lock 本身有它的效能成本,一般我們不會想要在每一個static function加上鎖定,
但這可能會造成Multi-Thread時發生問題,
Member A: 有需要Multi-Thread時再加就好!!
我: 當在撰寫底層元件是不見得會知道誰會來引用,只是留一個隱性的bug在那,等別人踩。
Member A: 在設計階段(UML)應該可以確定誰會來用吧?
我: 在專案時間壓力下, 不大可能在設計階段就到這麼詳細的設計, 而且在需求不斷變動下, 若沒有回頭修改, 仍然會有問題。
Member A: 那就都不要用static function
我: 這也太極端了, 像int , double , string 這種參數型的static function是安全的, 可以安心服用, 但物件的話, 應該要加入鎖定機制,
惟因為static 鎖定層級要到class, 會造成一般共用類別(Utility) 效能不佳, 故因將此類別拆分, 依內容, 效能考量, 使用率來分。
Member A: 其實我都是在(1)因為經常使用不想建立物件 (2)發現編譯失敗時, 才加入static修飾語, 從沒想那麼多.(冏)
我: 測試才是王道呀~"~ , 下面是完整的測試代碼
private void TestStaticFunc(int count, Type type, string methodName, object[] expecteds, object[] parms)
{
lock (this)
{
#region contract
if (count == 0)
return;
int eCount = expecteds.Count();
if (eCount < count)
{
throw new InvalidOperationException("Expected count is not enough." + count + "/" + eCount);
}
#endregion
List<Thread> pool = new List<Thread>(count);
for (int i = 0; i < count; i++)
{
object expected = expecteds[i];
Thread t = new Thread(() =>
{
object actual;
MethodInfo method = type.GetMethod(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static);
actual = method.Invoke(this, parms);
Assert.AreEqual(expected, actual);
});
pool.Add(t);
t.Name = "Thread-" + i;
t.Start();
}
WaitForQueue(pool);
}
}
注意:parms 是共用參數, 而非每次都new一個新的instance喔!!
TestStaticFunc(2, typeof(Class1), "RunObjectLock3", new object[] { new Class2() { x = 45 }, new Class2() { x = 90 } }, new object[] { new Class2() });