.NET 4并行編程之共享數(shù)據(jù)問題和解決概述
之前的文章介紹了了并行編程的一些基礎(chǔ)的知識,從本篇開始,將會講述并行編程中實(shí)際遇到一些問題,接下來的幾篇將會講述數(shù)據(jù)共享問題。
本篇的議題如下:
1.數(shù)據(jù)競爭
2.解決方案提出
3.順序的執(zhí)行解決方案
4.數(shù)據(jù)不變解決方案
在開始之前,首先,我們來看一個很有趣的例子:
- class BankAccount
- {
- public int Balance
- {
- get;
- set;
- }
- }
- class App
- {
- static void Main(string[] args)
- {
- // create the bank account instance
- BankAccount account = new BankAccount();
- // create an array of tasks
- Task[] tasks = new Task[10];
- for (int i = 0; i < 10; i++)
- {
- // create a new task
- tasks[i] = new Task(() =>
- {
- // enter a loop for 1000 balance updates
- for (int j = 0; j < 1000; j++)
- {
- // update the balance
- account.Balance = account.Balance + 1;
- }
- });
- // start the new task
- tasks[i].Start();
- }
- // wait for all of the tasks to complete
- Task.WaitAll(tasks);
- // write out the counter value
- Console.WriteLine("Expected value {0}, Counter value: {1}",
- 10000, account.Balance);
- // wait for input before exiting
- Console.WriteLine("Press enter to finish");
- Console.ReadLine();
- }
- }
10個task,每個task都是把BankAccount.Balance自增1000次。之后代碼就等到10個task執(zhí)行完畢,然后打印出Balance的值。大家猜想一下,上次的代碼執(zhí)行完成之后,打印出來的Balance的結(jié)果是多少?
J結(jié)果確實(shí)和大家猜想的一樣:結(jié)果不等于10000。每次執(zhí)行一次上面的代碼,都會得到不同的結(jié)果,而且這些結(jié)果值都在10000左右,如果運(yùn)氣好,可能看到有那么一兩次結(jié)果為10000.為什么會這樣?
下面就是本篇和接下來的幾篇文章要講述的內(nèi)容。
1.數(shù)據(jù)競爭
如果大家對多線程編程比較熟悉,就知道上面情況的產(chǎn)生是因?yàn)?“共享數(shù)據(jù)競爭”導(dǎo)致的(對多線程不熟悉不清楚的朋友也不用擔(dān)心)。當(dāng)有兩個或者更多的task在運(yùn)行并且操作同一個共享公共數(shù)據(jù)的時候,就存在潛在的競爭。如果不合理的處理競爭問題,就會出現(xiàn)上面意想不到的情況。
下面就來分析一下:上面代碼的情況是怎么產(chǎn)生的。
當(dāng)在把a(bǔ)ccount對象的Balance進(jìn)行自增的時候,一般執(zhí)行下面的三個步驟:
- 讀取現(xiàn)在account對象的Balance屬性的值。
- 計算,創(chuàng)建一個臨時的新變量,并且把Balance屬性的值賦值給新的變量,而且把新變量的值增加1
- 把新變量的值再次賦給account的Balance屬性
在理論上面,上面的三個步驟是代碼的執(zhí)行步驟,但是實(shí)際中,由于編譯器,.NET 運(yùn)行時對自增操作的優(yōu)化操作,和操作系統(tǒng)等的因素,在執(zhí)行上面代碼的時候,并不一定是按照我們設(shè)想的那樣運(yùn)行的,但是為了分析的方便,我們還是假設(shè)代碼是按照上面的三個步驟運(yùn)行的。
之前的代碼每次執(zhí)行一次,執(zhí)行代碼的計算機(jī)就每次處于不同的狀態(tài):CPU的忙碌狀況不同,內(nèi)存的剩余多少不同,等等,所以每次代碼的運(yùn)行,計算機(jī)不可能處于完全一樣的環(huán)境中。
在下面的圖中,顯示了兩個task之間是如何發(fā)生競爭的。當(dāng)兩個task啟動了之后(雖然說是并行運(yùn)算,但是不管這樣,兩個的task的執(zhí)行時間不可能完全一樣,也就是說,不可能恰好就是同時開始執(zhí)行的,起碼在開始執(zhí)行的時間上是有一點(diǎn)點(diǎn)的差異的)。
1. 首先Task1讀取到當(dāng)前的balance的值為0。
2. 然后,task2運(yùn)行了,并且也讀取到當(dāng)前的balance值為0。
3. 兩個task都把balance的值加1
4. Task1把balance的值加1后,把新的值保存到了balance中
5. Task2 也把新的保存到了balance中
所以,結(jié)果就是:雖然兩個task 都為balance加1,但是balance的值還是1。
通過這個例子,相信大家應(yīng)該清楚,為什么上面的10個task執(zhí)行1000,而執(zhí)行后的結(jié)果不是10000了。
2. 解決方案提出
數(shù)據(jù)競爭就好比一個生日party。其中,每一個task都是參加party的人,當(dāng)生日蛋糕出來之后,每個人都興奮了。如果此時,所有的人都一起沖過去拿屬于他們自己的那塊蛋糕,此時party就一團(tuán)糟了,沒有如何順序。
在之前的圖示例講解中,balance那個屬性就好比蛋糕,因?yàn)閠ask1,task2都要得到它,然后進(jìn)行運(yùn)算。當(dāng)我們來讓多個task共享一個數(shù)據(jù)時就可能出現(xiàn)問題。下面列出了四種解決方案:
1. 順序執(zhí)行:也就是讓第一個task執(zhí)行完成之后,再執(zhí)行第二個。
2. 數(shù)據(jù)不變:我們讓task不能修改數(shù)據(jù)。
3. 隔離:我們不共享數(shù)據(jù),讓每個task都有一份自己的數(shù)據(jù)拷貝。
4. 同步:通過調(diào)整task的執(zhí)行,有序的執(zhí)行task。
注意:同步和以前多線程中的同步,或者數(shù)據(jù)庫操作時的同步概念不一樣
3.順序的執(zhí)行的解決方案
順序的執(zhí)行解決了通過每次只有一個task訪問共享數(shù)據(jù)的方式解決了數(shù)據(jù)競爭的問題,其實(shí)在本質(zhì)上,這種解決方案又回到了之前的單線程編程模型。如果拿之前的party分蛋糕的例子,那么現(xiàn)在就是一次只能允許一個人去拿蛋糕。
4.數(shù)據(jù)不變解決方案
數(shù)據(jù)不變的解決方案就是通過讓數(shù)據(jù)不能被修改的方式來解決共享數(shù)據(jù)競爭。如果拿之前的蛋糕為例子,那么此時的情況就是:現(xiàn)在蛋糕只能看,不能吃。
在C#中,可以同關(guān)鍵字 readonly 和 const來聲明一個字段不能被修改:
public const int AccountNumber=123456;
被聲明為const的字段只能通過類型來訪問:如,上面的AccountNumber是在Blank類中聲明的,那么訪問的方式就是Blank. AccountNumber
readonly的字段可以在實(shí)例的構(gòu)造函數(shù)中修改。
如下代碼:
- using System;
- class ImmutableBankAccount
- {
- public const int AccountNumber = 123456;
- public readonly int Balance;
- public ImmutableBankAccount(int InitialBalance)
- {
- Balance = InitialBalance;
- }
- public ImmutableBankAccount()
- {
- Balance = 0;
- }
- }
- class App
- {
- static void Main(string[] args)
- {
- // create a bank account with the default balance
- ImmutableBankAccount bankAccount1 = new ImmutableBankAccount();
- Console.WriteLine("Account Number: {0}, Account Balance: {1}",
- ImmutableBankAccount.AccountNumber, bankAccount1.Balance);
- // create a bank account with a starting balance
- ImmutableBankAccount bankAccount2 = new ImmutableBankAccount(200);
- Console.WriteLine("Account Number: {0}, Account Balance: {1}",
- ImmutableBankAccount.AccountNumber, bankAccount2.Balance);
- // wait for input before exiting
- Console.WriteLine("Press enter to finish");
- Console.ReadLine();
- }
- }
數(shù)據(jù)不變的解決方案不是很常用,因?yàn)樗鼘?shù)據(jù)限制太大了。
原文標(biāo)題:.NET 并行(多核)編程系列之七 共享數(shù)據(jù)問題和解決概述
鏈接:http://www.cnblogs.com/yanyangtian/archive/2010/06/24/1764098.html
【編輯推薦】