關於C#多線程、易失域、鎖的分享

一、多線程

  windows系統是一個多線程的操作系統。一個程序至少有一個進程,一個進程至少有一個線程。進程是線程的容器,一個C#客戶端程序開始於一個單獨的線程,CLR(公共語言運行庫)爲該進程創建了一個線程,該線程稱爲主線程。例如當我們創建一個C#控制檯程序,程序的入口是Main()函數,Main()函數是始於一個主線程的。它的功能主要 是產生新的線程和執行程序。

  在軟件中,如果有一種操作可以被多人同時調用,我們就可以創建多個線程同時處理,以提高任務執行效率。這時,操作就被分配到各個線程中分別執行。

在C#中我們可以使用Thread類和ThreadStart委託,他們都定義在System.Threading命名空間中。

  ThreadStart委託類型用於定義在線程中的工作,就像我們在使用其他的委託類型一樣,可以使用方法名來創建此委託類型對象,如“new ThreadStart(test)”

多線程優點:
(1)多線程技術使程序的響應速度更快 ,因爲用戶界面可以在進行其它工作的同時一直處於活動狀態;
(2)多線程可以提高CPU的利用率,因爲當一個線程處於等待狀態的時候,CPU會去執行另外的線程;
(3)佔用大量處理時間的任務可以定期將處理器時間讓給其它任務;
(4)可以隨時停止任務;
(5)可以分別設置各個任務的優先級以優化性能。

多線程缺點:
(1)等候使用共享資源時造成程序的運行速度變慢。這些共享資源主要是獨佔性的資源 ,如寫文件等。
(2)對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。當這種負擔超過一定程度時,多線程的特點主要表現在其缺點上,比如用獨立的線程來更新數組內每個元素。
(3)線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程症狀。
(4)對公有變量的同時讀或寫。當多個線程需要對公有變量進行寫操作時,後一個線程往往會修改掉前一個線程存放的數據,從而使前一個線程的參數被修改;另外 ,當公用變量的讀寫操作是非原子性時,在不同的機器上,中斷時間的不確定性,會導致數據在一個線程內的操作產生錯誤,從而產生莫名其妙的錯誤,而這種錯誤是程序員無法預知的。

線程生命週期

線程生命週期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。

下面列出了線程生命週期中的各種狀態:

    • 未啓動狀態:當線程實例被創建但 Start 方法未被調用時的狀況。
    • 就緒狀態:當線程準備好運行並等待 CPU 週期時的狀況。
    • 不可運行狀態:下面的幾種情況下線程是不可運行的:
      • 已經調用 Sleep 方法
      • 已經調用 Wait 方法
      • 通過 I/O 操作阻塞
    • 死亡狀態:當線程已完成執行或已中止時的狀況

Thread 類常用的屬性和方法

  

最簡單的多線程例子,代碼如下:

static void Main(string[] agrs)
        {
            ThreadStart threadWork = new ThreadStart(test);
            Thread t1 = new Thread(threadWork);
            t1.Name = "t1";
            Thread t2 = new Thread(threadWork);
            t2.Name = "t2";
            Thread t3 = new Thread(threadWork);
            t3.Name = "t3";
            //開始執行
            t1.Start();
            t2.Start();
            t3.Start();
            Console.ReadKey();
        }
        static public void  test(){
            Console.WriteLine("{0},hello,小菜鳥",Thread.CurrentThread.Name);
        }

使用多線程另一個重要的問題就是對於公共資源分配的控制,比如,火車的座位是有限的,在不同購票點買票時,就需要對座位資源進行合理分配;在電影院看電影也是這樣的,座位只有那麼多,我們不可能100個座位賣出200張票,這樣是不可以的也是不應該的,那麼接下來我們就要看看該如何解決這個問題。

二、易失域

對於類中的成員使用volatile修飾符,它就會被聲明爲易失域。對於易失域,在多線程環境中,每個線程中對此域的讀取(易失讀取,volatile read)和寫入(易失寫入,volatile write)操作都會觀察其他線程中的操作,並進行操作的順序執行,這樣就保持易失域使用的一致性了。

volatile的作用是: 作爲指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。多線程的程序,共同訪問的內存當中,多個線程都可以操縱,從而無法判定何時這個變量會發生變化

可以這樣簡單理解:線程是並行的,但對volatile的訪問是順序排除的,避免出現髒值。

理解:

Volatile 字面的意思時易變的,不穩定的。在C#中也差不多可以這樣理解。

編譯器在優化代碼時,可能會把經常用到的代碼存在Cache裏面,然後下一次調用就直接讀取Cache而不是內存,這樣就大大提高了效率。但是問題也隨之而來了。

在多線程程序中,如果把一個變量放入Cache後,又有其他線程改變了變量的值,那麼本線程是無法知道這個變化的。它可能會直接讀Cache裏的數據。但是很不幸,Cache裏的數據已經過期了,讀出來的是不合時宜的髒數據。這時就會出現bug。

用Volatile聲明變量可以解決這個問題。用Volatile聲明的變量就相當於告訴編譯器,我不要把這個變量寫Cache,因爲這個變量是可能發生改變的。

下面貼栗子代碼:

using System;
using System.Threading;
 
namespace demoVolatile
{
    class Program
    {
        //多個線程訪問的變量,標記爲Volatile
        //在這裏如果不標記可能會賣出不止10張票
        volatile static int TicketCount = 10;
        static void SellTicket()
        {
            while (TicketCount > 0)
            {
                TicketCount--;
                Console.WriteLine("{0} 賣出了一張票", Thread.CurrentThread.Name);
            }
            Console.WriteLine("{0} 下班了", Thread.CurrentThread.Name);
        }
        static void Main(string[] args)
        {
            ThreadStart threadWork = new ThreadStart(SellTicket);
            Thread t1 = new Thread(threadWork);
            t1.Name = "t1";
            Thread t2 = new Thread(threadWork);
            t2.Name = "t2";
            Thread t3 = new Thread(threadWork);
            t3.Name = "t3";
            //開始執行
            t1.Start();
            t2.Start();
            t3.Start();
            Console.ReadKey();
        }
    }
}

三、鎖

我們都知道,lock 關鍵字可以用來確保代碼塊完成運行,而不會被其他線程中斷。也就是,說在多線程中,使用lock關鍵字,可以讓被lock的對象,一次只被一個線程使用。

lock語句根本使用的就是Monitor.Enter和Monitor.Exit,也就是說lock(this)時執行Monitor.Enter(this),大括號結束時執行Monitor.Exit(this). 也就是說,Lock關鍵字,就是一個語法糖而已。

使用lock需要注意的地方:
1.lock不能鎖定空值某一對象可以指向Null,但Null是不需要被釋放的。(請參考:認識全面的null)
2.lock不能鎖定string類型,雖然它也是引用類型的。因爲字符串類型被CLR“暫留”
3.lock鎖定的對象是一個程序塊的內存邊界
4.值類型不能被lock,因爲前文標紅字的“對象被釋放”,值類型不是引用類型的
5.lock就避免鎖定public 類型或不受程序控制的對象。

using System;
using System.Threading.Tasks;

public class Account
{
    private readonly object balanceLock = new object();
    private decimal balance;

    public Account(decimal initialBalance)
    {
        balance = initialBalance;
    }

    public decimal Debit(decimal amount)
    {
        lock (balanceLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine($"Balance before debit :{balance, 5}");
                Console.WriteLine($"Amount to remove     :{amount, 5}");
                balance = balance - amount;
                Console.WriteLine($"Balance after debit  :{balance, 5}");
                return amount;
            }
            else
            {
                return 0;
            }
        }
    }

    public void Credit(decimal amount)
    {
        lock (balanceLock)
        {
            Console.WriteLine($"Balance before credit:{balance, 5}");
            Console.WriteLine($"Amount to add        :{amount, 5}");
            balance = balance + amount;
            Console.WriteLine($"Balance after credit :{balance, 5}");
        }
    }
}

class AccountTest
{
    static void Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => RandomlyUpdate(account));
        }
        Task.WaitAll(tasks);
    }

    static void RandomlyUpdate(Account account)
    {
        var rnd = new Random();
        for (int i = 0; i < 10; i++)
        {
            var amount = rnd.Next(1, 100);
            bool doCredit = rnd.NextDouble() < 0.5;
            if (doCredit)
            {
                account.Credit(amount);
            }
            else
            {
                account.Debit(amount);
            }
        }
    }
}

作用:當同一個資源被多個線程讀,少個線程寫的時候,使用讀寫鎖

引用:https://blog.csdn.net/weixin_40839342/article/details/81189596 

問題: 既然讀讀不互斥,爲何還要加讀鎖

答:     如果只是讀,是不需要加鎖的,加鎖本身就有性能上的損耗

            如果讀可以不是最新數據,也不需要加鎖

            如果讀必須是最新數據,必須加讀寫鎖

            讀寫鎖相較於互斥鎖的優點僅僅是允許讀讀的併發,除此之外並無其他。

注意:不要使用ReaderWriterLock,該類有問題

 

ok,今天的分享就到這裏了,如有錯誤的地方請指出,謝謝。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章