[static]Static Function 的小實驗

[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() });