.Net 線程與鎖


一台服務器能運行多少個線程,大致取決於CPU的管理能力。CPU負責線程的創建、協調、切換、銷毀、暫停、喚醒、運行等。
一個應用程序中,必須有一個進程維持應用程序的運行環境,一個進程可同時有多個線程協作處理應用邏輯。

同步:單線程,每一步都執行結束並返回結果,下一步處於等待,阻塞程序流

異步:多線程,不需要等待執行結束,可繼續執行下一步,形成並行處理,無序的不可預測的執行順序

前台線程:主線程退出后,子線程直至完成計算。

后台線程:主線程退出后,子線程也會停止退出。

作者:[Sol·wang] - 博客園,原文出處:https://www.cnblogs.com/Sol-wang/p/14793008.html

一、線程的應用

常見的線程應用方式

  • new Thread
  • ThreadPool.QueueUserWorkItem 后台線程
  • Task.Run / Task.Factory.StartNewd
  • Parallel
  • await / async

ThreadPool 線程池

線程池線程是后台線程。 每個線程均使用默認的堆棧大小,以默認的優先級運行,並且位於多線程單元中。 一旦線程池中的線程完成任務,它將返回到等待線程隊列中。 這時開始即可重用它。 通過這種重復使用,應用程序可以避免產生為每個任務創建新線程的開銷。

每個進程只有一個線程池。由線程池統一管理每個線程的創建/分配/銷毀。

// 設置可同時並行運行的線程數
ThreadPool.SetMinThreads
ThreadPool.SetMaxThreads
// 用線程池中的后台線程執行方法
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 參數);

Task

Task所用到的線程同樣來自於ThreadPool。所以通過ThreadPool線程數量的設置有助於Task的線程管理。

// Action:有參    (Action 與 Func 的區別:Action不能有返回值,Func必須有返回值)
Action<string> action = (string a) =>
{
    Console.WriteLine($"有參Action\tparams {a}\tTId {Thread.CurrentThread.ManagedThreadId}");
};

// Create a task
Task t1 = new Task(action, "alpha");
t1.Start();

// Task.Factory
Task t2 = Task.Factory.StartNew(action, "beta");
t2.Start();



// Task 的返回值(等待返回結果)
Task<int> task = new Task<int>(() =>
{
    //...
    return 0;
});
task.Start();
int result = task.Result;


// 僅僅啟動一個Task
Task.Run(() =>
{
    //...
});


// 批量啟動 Task
// 同時並行運行至少30個線程的方式
ThreadPool.SetMinThreads(30, 50);
ThreadPool.SetMaxThreads(50, 70);
List<Task> tks = new List<Task>();
for (int i = 0; i < 30; i++)
{
    tks.Add(Task.Run(() =>
    {
        //...
    }));
}
// 等待線程全部運行結束
Task.WaitAll(tks.ToArray());



// 取消線程運行
// 更多取消方式參考 https://learn.microsoft.com/zh-cn/dotnet/standard/threading/cancellation-in-managed-threads
CancellationTokenSource cts = new CancellationTokenSource();
Task.Run(() =>
{
    if (Console.ReadKey(true).KeyChar.ToString().ToUpperInvariant() == "C")
    {
        // 取消
        cts.Cancel();
    }
});

Parallel

多線程並行處理的Parallel.InvokeParallel.ForParallel.ForEach;無法確保順序處理。

// 示例:並行運行幾個方法
Parallel.Invoke(方法1, 方法2, () => 方法3, () => 方法4)



// ParallelOptions 參數設定
// 先指定啟用30個線程同時處理的設置
ParallelOptions paroptions = new ParallelOptions();
paroptions.MaxDegreeOfParallelism = 30;



// 指定數量的線程,並行執行200遍
List<int> datas = new List<int>();
Parallel.For(0, 200, paroptions, index =>
{
    datas.add(index);
});



// 指定數量的線程,並行讀取集合數據
Parallel.ForEach(datas, paroptions, (item) =>
{
    Console.WriteLine(item);
});

await / async

創建后台線程並行處理,並取得處理結果。

// 執行 async 方法(后台子線程執行)
Task<string> _task_result_1 = t1.Func1();
// 主線程不等返回結果,繼續往下執行

// 繼續執行方法
string _result_2 = t1.Func2();
// 以上兩個方法 Func1()、Func2() 並行執行

// 取 Func1 的運行結果
string _result_1 = await _task_result_1;

// 整合兩個方法的運行結果
int _total_ = _result_1.length + _result_2.length;

async 定義方法有返回值時用Task<T> 。
用 await 調用 async 定義的方法時,多線程,阻塞式同步執行。
不用 await 調用 async 定義的方法時,多線程,非阻塞異步執行。

二、常用的線程鎖

lock

通用的標准鎖,封裝自應用級鎖Monitor類,需要有線程共享的鎖標識,告知其它線程是否等待。

Monitor

程序級鎖,於單個應用范圍內,通過獲取或釋放用於標識資源 T 來授予對共享資源的相互獨占訪問權限。

// 給要操作的對象加把鎖,排他鎖
Monitor.Enter(T);
// 釋放當前鎖,允許其它線程使用了
Monitor.Exit(T);

SpinLock

快速的、低級別的簡易鎖。不同於標准鎖,適合應用於簡單的、非耗時的邏輯處理,此場景下更多時候性能優於標准鎖。

如果應用場景並非足夠簡單或存在不確定性的可能,SpinLock 將比標准鎖開銷更大。

static SpinLock _spinlock = new SpinLock();


bool lockTaken = false;
try
{
    // 加鎖
    _spinlock.Enter(ref lockTaken);
    // ...
}
finally
{
    // 釋放
    if (lockTaken) _spinlock.Exit(false);
}

Interlocked

更細化的鎖,針對性的場景時用來代替lock,包括非空、遞增等針對場景的應用,性能優於lock

// 遞增場景案例

// lock 方式
lock(lockObject)
{  
    myField++;  
}

// Interlocked 的替代方式
System.Threading.Interlocked.Increment(myField);

Semaphore

進程間同步,跨應用限制可同時訪問某一資源或資源池的線程數,允許同時共享的線程數

// 創建時,定義同時的默認線程數和最多線程數(起始線程數,最多線程數)
Semaphore sem = new Semaphore(10, 20);
// 上鎖
sem.WaitOne();
// 釋放鎖(一次釋放線程數)
sem.Release(3);

應用場景:假如多線程寫入DB,當給DB類上鎖時,可結合DB的max_connection值,設置允許同時訪問的線程數。

Mutex

進程間同步,系統級鎖,所以可跨進程跨應用。
// 阻擋鎖住當前塊,其它線程處於等待
// 超時后返回false,不允許其它線程進入
Mutex.WaitOne(timeout);
// 釋放當前,一個,允許其它線程進入
Mutex.ReleaseMutex();

三、線程安全

當多個線程訪問同一個對象的屬性或方法時,對這些調用進行同步處理是非常重要的。如果不用考慮這些線程在運行時環境下的調度和交替執行,也不需要進行額外的同步或者在調用方進行任何其它的協調操作,調用這個對象的行為都可以獲得有效的結果,其成員不受中斷影響的類,那這個對象是線程安全的。否則,一個線程可能會中斷另一個線程正在執行的任務,可能使該對象處於無效狀態。

通常多個線程在調用同一共享資源,為了解決線程間的同步和互相干擾中斷,會對共享資源加鎖,使其線程間有序的獨占運行。

在早期的.NET版本中,提供了常用的線程安全類,通過Synchronized方法創建的實例實現線程同步。
如:ArrayList,Hashtable

// Synchronized 方法案例

// 線程安全創建對象,並實現多線程同步
Hashtable ht = Hashtable.Synchronized(new Hashtable());
// 多線程調用 不用鎖
ht.Add("key1", true);

線程安全的高性能集合類

.NET Framework 4 引入了幾種專為支持多線程添加和刪除操作而設計的集合類型。 為了實現線程安全,這些類型使用多種高效的鎖定和免鎖定同步機制。 同步會增加操作的開銷。 開銷數取決於所用的同步類型、執行的操作類型和其他因素,例如嘗試並行訪問該集合的線程數。

System.Collections.Concurrent 命名空間,其中包含多個線程安全且可縮放的集合類。 多個線程可以安全高效地從這些集合添加或刪除項,而無需在用戶代碼中進行其他同步。 編寫新代碼時,只要將多個線程同時寫入到集合時,就使用並發集合類。

直接在其它線程中對並發集合操作,不需要手動加鎖:

ConcurrentBag  無序元素集合的線程安全實現

ConcurrentStack  LIFO(后進先出)堆棧的線程安全實現

ConcurrentQueue  FIFO(先進先出)隊列的線程安全實現

ConcurrentDictionary  鍵值對字典的線程安全實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM