C#多線程(一)線程基礎篇

来源:https://www.cnblogs.com/xiaolipro/archive/2022/11/13/16885592.html
-Advertisement-
Play Games

線程基礎 視頻已經發佈到B站 參考文章: 《Threading in C# 》(Joseph Albahari)https://www.albahari.com/threading/ 《Threading in C# 》中文翻譯(GKarch ):https://blog.gkarch.com/to ...


線程基礎

視頻已經發佈到B站

參考文章:

《Threading in C# 》(Joseph Albahari)https://www.albahari.com/threading/

《Threading in C# 》中文翻譯(GKarch ):https://blog.gkarch.com/topic/threading.html

《圖解系統》(小林coding):https://xiaolincoding.com/os/

一、概念

並行(parallel):同一時間,多個線程/進程同時執行。多線程的目的就是為了並行,充分利用cpu多個核心,提高程式性能

線程(threading):線程是操作系統能夠進行 運算調度的最小單位,是進程的實際運作單位。
一條線程指的是進程中一個單一順序的控制流,一個進程中可以並行多個線程,每條線程並行執行不同的任務。

進程(process):進程是操作系統進行資源分配的基本單位。多個進程並行的在電腦上執行,多個線程並行的在進程中執行,
進程之間是隔離的,線程之間共用堆,私有棧空間。

CLR 為每個線程分配各自獨立的 棧(stack) 空間,因此局部變數是線程獨立的。

static void Main()
{
  new Thread(Go).Start();  // 在新線程執行Go()
  Go();  // 在主線程執行Go()
}

static void Go()
{
  // 定義和使用局部變數 - 'cycles'
  for (int cycles = 0; cycles < 5; cycles++) Console.Write ('?');
}

變數cycles的副本是分別線上程各自的棧中創建,因此會輸出 10 個問號

??????????

線程可以通過對同一對象的引用來共用數據。例如:

static bool done = false;

static void Main()
{
  new Thread (tt.Go).Start(); // A
  Go(); // B
}

static void Go()
{
   if (!done) { 
      Console.WriteLine ("Done");
      done = true;
   }
}

這個例子引出了一個關鍵概念 線程安全(thread safety) ,由於併發,” Done “ 有可能會被列印兩次

通過簡單的加鎖操作:在讀寫公共欄位時,獲得一個 排它鎖(互斥鎖,exclusive lock ) ,c#中使用lock即可生成 臨界區(critical section)

static readonly object locker = new object();
...
static void Go()
{
  lock (locker) // B
  {
    if (!done) { 
      Console.WriteLine ("Done");
      done = true;
    }
  }
}

臨界區(critical section):在同一時刻只有一個線程能進入,不允許併發。當有線程進入臨界區段時,其他試圖進入的線程或是進程必須 等待或阻塞(blocking)

線程阻塞(blocking):指一個線程在執行過程中暫停,以等待某個條件的觸發來解除暫停。阻塞狀態的線程不會消耗CPU資源

掛起(Suspend):和阻塞非常相似,在虛擬記憶體管理的操作系統中,通常會把阻塞狀態的進程的物理記憶體空間換出到硬碟,等需要再次運行的時候,再從硬碟換入到物理記憶體。描述進程沒有占用實際的物理記憶體空間的情況,這個狀態就是掛起狀態

可以通過調用Join方法等待線程執行結束,例如:

static void Main()
{
  Thread t = new Thread(Go);
  t.Start();
  t.Join();  // 等待線程 t 執行完畢
  Console.WriteLine ("Thread t has ended!");
}

static void Go()
{
  for (int i = 0; i < 1000; i++) Console.Write ("y");
}

也可以使用Sleep使當前線程阻塞一段時間:

Thread.Sleep (500);  // 阻塞 500 毫秒

Thread.Sleep(0)會立即釋放當前的時間片(time slice),將 CPU 資源出讓給其它線程。Framework 4.0的Thread.Yield()方法與其大致相同,不同的是Yield()只會出讓給運行在相同處理器核心上的其它線程。

Sleep(0)和Yield在調整代碼性能時偶爾有用,它也是一個很好的診斷工具,可以用於找出線程安全(thread safety)的問題。如果在你代碼的任意位置插入Thread.Yield()會影響到程式,
基本可以確定存在 bug。

二、原理

硬體結構

https://xiaolincoding.com/os/1_hardware/how_cpu_run.html#圖靈機的工作方式

運行時

  線程在內部由一個 線程調度器(thread scheduler) 管理,一般 CLR 會把這個任務交給操作系統完成。線程調度器確保所有活動的線程能夠分配到適當的執行時間,並且保證那些處於等待或阻塞狀態(例如,等待排它鎖或者用戶輸入)的線程不消耗CPU時間。

  在單核電腦上,線程調度器會進行 時間切片(time-slicing) ,快速的在活動線程中切換執行。在 Windows 操作系統上,一個時間片通常在十幾毫秒(譯者註:預設 15.625ms),遠大於 CPU 線上程間進行上下文切換的開銷(通常在幾微秒區間)。

  在多核電腦上,多線程的實現是混合了時間切片和 真實的併發(genuine concurrency) ,不同的線程同時運行在不同的 CPU 核心上。仍然會使用到時間切片,因為操作系統除了要調度其它的應用,還需要調度自身的線程。

  線程的執行由於外部因素(比如時間切片)被中斷稱為 被搶占(preempted)。在大多數情況下,線程無法控制其在什麼時間,什麼代碼塊被搶占。

  多線程同樣也會帶來缺點,最大的問題在於它提高了程式的複雜度。使用多個線程本身並不複雜,複雜的是線程間的交互(共用數據)如何保證安全。無論線程間的交互是否有意為之,都會帶來較長的開發周期,以及帶來間歇的、難以重現的 bug。因此,最好保證線程間的交互儘可能少,並堅持簡單和已被證明的多線程交互設計。

  當頻繁地調度和切換線程時(且活動線程數量大於 CPU 核心數),多線程會增加系統資源和 CPU 的開銷,線程的創建和銷毀也會增加開銷。多線程並不總是能提升程式的運行速度,如果使用不當,反而可能降低速度。

三、基礎

創建與啟動

使用Thread類的構造方法來創建線程,支持以下兩種委托

public delegate void ThreadStart();

public delegate void ParameterizedThreadStart (object? obj);

關於Thread構造重載方法參數 maxStackSize,不建議使用

https://stackoverflow.com/questions/5507574/maximum-thread-stack-size-net

public void 創建一個線程()
{
    var t = new Thread(Go);  // 開一個線程t
    t.Start();  // 啟動t線程,執行Go方法
    
    Go();  // 主線程執行Go方法
}

void Go()
{
    _testOutputHelper.WriteLine("hello world!");
}

每一個線程都有一個 Name 屬性,我們可以設置它以便於調試。線程的名字只能設置一次,再次修改會拋出異常。

public void 線程命名()
{
    var t = new Thread(Go);  // 開一個線程t
    t.Name = "worker";
    t.Start();  // 啟動t線程,執行Go方法
    
    Go();  // 主線程執行Go方法
}

void Go()
{
    // Thread.CurrentThread屬性會返回當前執行的線程
    _testOutputHelper.WriteLine(Thread.CurrentThread.Name + " say: hello!");
}

傳遞參數

Thread類的Start方法重載支持向thread實例傳參

public void Start(object? parameter)

參數被lambda表達式捕獲,傳遞給Go方法

public void 創建一個線程()
{
    var t = new Thread(msg => Go(msg));  // 開一個線程t
    t.Start("hello world!");  // 啟動t線程,執行Go方法
    
    Go("main thread say:hello world!");  // 主線程執行Go方法
}

void Go(object? msg)
{
    _testOutputHelper.WriteLine(msg?.ToString());
}

請務必註意,不要在啟動線程之後誤修改被捕獲變數(captured variables)

public void 閉包問題()
{
    for (int i = 0; i < 10; i++)
    {
        new Thread (() => Go(i)).Start();
    }
}

前臺/後臺線程

預設情況下,顯式創建的線程都是前臺線程(foreground threads)。只要有一個前臺線程在運行,程式就可以保持存活不結束。
當一個程式中所有前臺線程停止運行時,仍在運行的所有後臺線程會被強制終止。

這裡說的 顯示創建,指的是通過new Thread()創建的線程

非預設情況,指的是將Thread的IsBackground屬性設置為true

static void Main (string[] args)
{
	Thread worker = new Thread ( () => Console.ReadLine() );
	if (args.Length > 0) worker.IsBackground = true;
	worker.Start();
}

當進程以強制終止這種方式結束時,後臺線程執行棧中所有finally塊就會被避開。如果程式依賴finally(或是using)塊來執行清理工作,例如釋放資料庫/網路連接或是刪除臨時文件,就可能會產生問題。
為了避免這種問題,在退出程式時可以顯式的等待這些後臺線程結束。有兩種方法可以實現:

  • 如果是顯式創建的線程,線上程上調用Join阻塞。
  • 如果是使用線程池線程,使用信號構造,如事件等待句柄。

在任何一種情況下,都應指定一個超時時間,從而可以放棄由於某種原因而無法正常結束的線程。這是後備的退出策略:我們希望程式最後可以關閉,而不是讓用戶去開任務管理器(╯-_-)╯╧══╧

線程的 前臺/後臺狀態 與它的 優先順序/執行時間的分配無關。

異常處理

當線程開始運行後,其內部發生的異常不會拋到外面,更不會被外面的try-catch-finally塊捕獲到。

void 異常捕獲()
{
    try
    {
        new Thread(Go).Start();  // 啟動t線程,執行Go方法
    }
    catch (Exception e)
    {
        _testOutputHelper.WriteLine(e.Message);
    }
}
    
void Go() => throw null!;  // 拋出空指針異常

解決方案是將異常處理移到Go方法中:自己的異常,自己解決

static void Go()
{
  try
  {
    // ...
    throw null;    // 異常會在下麵被捕獲
    // ...
  }
  catch (Exception ex)
  {
    // 一般會記錄異常,或通知其它線程我們遇到問題了
    // ...
  }
}

AppDomain.CurrentDomain.UnhandledException 會對所有未處理的異常觸發,因此它可以用於集中記錄線程發生的異常,但是它不能阻止程式退出。

void UnhandledException()
{
    AppDomain.CurrentDomain.UnhandledException += HandleUnHandledException;
    new Thread(Go).Start();  // 啟動t線程,執行Go方法
}

void HandleUnHandledException(object sender, UnhandledExceptionEventArgs eventArgs)
{
    _testOutputHelper.WriteLine("我發現異常了");
}

並非所有線程上的異常都需要處理,以下情況,.NET Framework 會為你處理:

  • 非同步委托(APM)
  • BackgroundWorker(EAP)
  • 任務並行庫(TPL)

中斷與中止

所有阻塞方法Wait(), Sleep() or Join(),在阻塞條件永遠無法被滿足且沒有指定超時時間的情況下,線程會陷入永久阻塞。

有兩個方式可以實現強行結束:中斷、中止

中斷(Interrupt)

在一個阻塞線程上調用Thread.Interrupt會強制釋放它,並拋出ThreadInterruptedException異常,與上文的一樣,這個異常同樣不會拋出

var t = new Thread(delegate()
{
    try
    {
        Thread.Sleep(Timeout.Infinite);  // 無期限休眠
    }
    catch (ThreadInterruptedException)
    {
        _testOutputHelper.WriteLine("收到中斷信號");
    }

    _testOutputHelper.WriteLine("溜溜球~");
});
t.Start();
Thread.Sleep(3000);  // 睡3s後中斷線程t
t.Interrupt();

如果在非阻塞線程上調用Thread.Interrupt,線程會繼續執行直到下次被阻塞時,拋出ThreadInterruptedException。這避免了以下這樣的代碼:

if ((worker.ThreadState & ThreadState.WaitSleepJoin) > 0)  // 線程不安全的
{
    worker.Interrupt();
}

  隨意中斷一個線程是極度危險的,這可能導致調用棧上的任意方法(框架、第三方包)收到意外的中斷,而不僅僅是你自己的代碼!只要調用棧上發生阻塞(因為使用同步構造),
中斷就會發生在這,如果在設計時沒有考慮中斷(在finally塊中執行適當清理),線程中的對象就可能成為一個奇怪狀態(不可用或未完全釋放)。

  如果是自己設計的阻塞,完全可以用 信號構造(signal structure) 或者 取消令牌(cancellation tokens) 來達到相同效果,且更加安全。如果希望結束他人代碼導致的阻塞,Abort總是更合適

中止(Abort)

通過Thread.Abort方法也可以使阻塞的線程被強制釋放,效果和調用Interrupt類似,不同的是它拋出的是ThreadAbortException的異常。另外,這個異常會在catch塊結束時被重新拋出(試圖更好的結束線程)。

Thread t = new Thread(delegate()
{
    try
    {
        while (true)
        {
        }
    }
    catch (ThreadAbortException)
    {
        _testOutputHelper.WriteLine("收到中止信號");
    }
    // 這裡仍然會繼續拋出ThreadAbortException,以保證此線程真正中止
});

_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Unstarted 狀態

t.Start();
Thread.Sleep(1000);
_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Running 狀態

t.Abort();
_testOutputHelper.WriteLine(t.ThreadState.ToString()); // AbortRequested 狀態

t.Join();
_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Stopped 狀態

除非Thread.ResetAbort在catch塊中被調用,在此之前,線程狀態(thread state) 是AbortRequested,調用Thread.ResetAbort來阻止異常被自動重新拋出之後,
線程重新進入Running狀態(從這開始,它可能被再次中止)

static void Main()
{
  Thread t = new Thread (Work);
  t.Start();
  Thread.Sleep (1000); t.Abort();
  Thread.Sleep (1000); t.Abort();
  Thread.Sleep (1000); t.Abort();
}

static void Work()
{
  while (true)
  {
    try { while (true); }
    catch (ThreadAbortException) { Thread.ResetAbort(); }
    Console.WriteLine ("我沒死!");
  }
}

Thread.Abort在NET 5被棄用了:https://learn.microsoft.com/zh-cn/dotnet/core/compatibility/core-libraries/5.0/thread-abort-obsolete

未處理的ThreadAbortException是僅有的兩個不會導致應用程式關閉的異常之一,另一個是AppDomainUnloadException。

Abort幾乎對處於任何狀態的線程都有效:Running、Blocked、Suspended以及Stopped。然而,當掛起的線程被中止時,會拋出ThreadStateException異常。中止會直到線程之後恢復時才會起作用。

try { suspendedThread.Abort(); }
catch (ThreadStateException) { suspendedThread.Resume(); }
// 現在 suspendedThread 才會中止

Interrupt和Abort最大的不同是:調用Interrupt線程會繼續工作直到下次被阻塞時拋出異常,而調用Abort會立即線上程正在執行的地方拋出異常(非托管代碼除外)。

這將導致一個新的問題:.NET Framework 中的代碼可能會被中止,而且不是安全的中止。如果中止發生在FileStream被構造期間,很可能造成一個非托管文件句柄會一直保持打開直到應用程式域結束。

協作取消模式

正如上面所說Interrupt和Abort總是危險的,替代方案就是實現一個協作模式(cooperative ):工作線程定期檢查一個用於指示是否應該結束的標識,發起者只需要設置這個標識,等待工作線程響應,即可取消線程執行。

Framework 4.0 提供了兩個類CancellationTokenSourceCancellationToken來完成這個模式:

  • CancellationTokenSource定義了Cancel方法。
  • CancellationToken定義了IsCancellationRequested屬性和ThrowIfCancellationRequested方法。
void 取消令牌()
{
    var cancelSource = new CancellationTokenSource();
    cancelSource.CancelAfter(3000);
    var t = new Thread(() => Work(cancelSource.Token));
    t.Start();
    t.Join();
}
void Work(CancellationToken cancelToken)
{
    while (true)
    {
        cancelToken.ThrowIfCancellationRequested();
        // ...
        Thread.Sleep(1000);
    }
}

四、非同步編程模式

MSDN文檔:https://learn.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/

非同步編程模型(APM)

非同步編程模型(Asynchronous Programming Model),提出於.NET Framework 1.x 的時代,基於IAsyncResult介面實現類似BeginXXX和EndXXX的方法。

APM是建立在委托之上的,Net Core中的委托 不支持非同步調用,也就是 BeginInvoke 和 EndInvoke 方法。

void APM()
{
    var uri = new Uri("https://www.albahari.com/threading/part3.aspx");
    Func<Uri, int> f = CalcUriStringCount;
    var res = f.BeginInvoke(uri, null, null);
    // do something
    _testOutputHelper.WriteLine("我可以做別的事情");
    _testOutputHelper.WriteLine("共下載字元數:" + f.EndInvoke(res));
}
int CalcUriStringCount(Uri uri)
{
    var client = new WebClient();
    var res = client.DownloadString(uri);
    return res.Length;
}

EndInvoke會做三件事:

  1. 如果非同步委托還沒有結束,它會等待非同步委托執行完成。
  2. 它會接收返回值(也包括refout方式的參數)。
  3. 它會向調用線程拋出未處理的異常。

不要因為非同步委托調用的方法沒有返回值就不調用EndInvoke,因為這將導致其內部的異常無法被調用線程察覺。MSDN文檔中明確寫了 “無論您使用何種方法,都要調用 EndInvoke 來完成非同步調用。”

BeginInvoke也可以指定一個回調委托。這是一個在完成時會被自動調用的、接受IAsyncResult對象的方法。

BeginInvoke的最後一個參數是一個用戶狀態對象,用於設置IAsyncResultAsyncState屬性。它可以是需要的任何東西,在這個例子中,我們用它向回調方法傳遞method委托,這樣才能夠在它上面調用EndInvoke

var uri = new Uri("https://www.albahari.com/threading/part3.aspx");
Func<Uri, int> func = CalcUriStringCount;
var res = func.BeginInvoke(uri, new AsyncCallback(res =>
{
    var target = res.AsyncState as Func<string, int>;
    _testOutputHelper.WriteLine("共下載字元數:" + target!.EndInvoke(res));
    _testOutputHelper.WriteLine("非同步狀態:" + res.AsyncState);
}), func);
// do something
_testOutputHelper.WriteLine("我可以做別的事情");
func.EndInvoke(res);

基於事件的非同步模式(EAP)

基於事件的非同步模式(event-based asynchronous pattern),EAP 是在 .NET Framework 2.0 中提出的,讓類可以提供多線程的能力,而不需要使用者顯式啟動和管理線程。這種模式具有以下能力:

  • 協作取消模型(cooperative cancellation model)
  • 線程親和性(thread affinity)
  • 將異常轉發到完成事件(forwarding exceptions)

這個模式本質上就是:類提供一組成員,用於在內部管理多線程,類似於下邊的代碼:

// 這些成員來自於 WebClient 類:

public byte[] DownloadData (Uri address);    // 同步版本
public void DownloadDataAsync (Uri address);
public void DownloadDataAsync (Uri address, object userToken);
public event DownloadDataCompletedEventHandler DownloadDataCompleted;

public void CancelAsync (object userState);  // 取消一個操作
public bool IsBusy { get; }                  // 指示是否仍在運行

當調用基於EAP模式的類的XXXAsync方法時,就開始了一個非同步操作,EAP模式是基於APM模式之上的。

var client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
    if (args.Cancelled) _testOutputHelper.WriteLine("已取消");
    else if (args.Error != null) _testOutputHelper.WriteLine("發生異常:" + args.Error.Message);
    else
    {
        _testOutputHelper.WriteLine("共下載字元數:" + args.Result.Length);
        // 可以在這裡更新UI。。
    }
};
_testOutputHelper.WriteLine("我在做別的事情");
client.DownloadStringAsync(new Uri("https://www.albahari.com/threading/part3.aspx"));

BackgroundWorker是命名空間System.ComponentModel中的一個工具類,用於管理工作線程。它可以被認為是一個 EAP 的通用實現,在EAP功能的基礎上額外提供了:

  • 報告工作進度的協議
  • 實現了IComponent介面

另外BackgroundWorker使用了線程池,意味著絕不應該在BackgroundWorker線程上調用Abort

void 工作進度報告()
{
    worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;  // 支持進度報告
    worker.WorkerSupportsCancellation = true;  // 支持取消
    worker.DoWork += DoWoker;
    worker.ProgressChanged += (_, args) => _testOutputHelper.WriteLine($"當前進度:{args.ProgressPercentage}%");
    worker.RunWorkerCompleted += (sender, args) =>
    {
        if (args.Cancelled) _testOutputHelper.WriteLine("工作線程已被取消");
        else if (args.Error != null) _testOutputHelper.WriteLine("工作線程發生異常: " + args.Error);
        else _testOutputHelper.WriteLine("任務完成,結果: " + args.Result); // Result來自DoWork
    };
    
    worker.RunWorkerAsync();
}

private void DoWoker(object? sender, DoWorkEventArgs e)
{
    for (int i = 0; i < 100; i+= 10)
    {
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
        worker.ReportProgress(i);  // 上報進度
        Thread.Sleep(1000);  // 模擬耗時任務
    }

    e.Result = int.MaxValue;  // 這個值會回傳給RunWorkerCompleted
}

基於任務的非同步模式 (TAP)

從 .NET Framework 4 開始引入

五、拓展知識

小林coding:https://xiaolincoding.com/os/4_process/process_base.html#進程的控制結構

線程優先順序

線程的Priority屬性決定了相對於操作系統中的其它活動線程,它可以獲得多少CPU 時間片(time slice)

優先順序依次遞增,在提升線程優先順序前請三思,這可能會導致其它線程的 資源饑餓(resource starvation)

enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

提升線程的優先順序並不等於直接優先,因為線程還受進程優先順序影響,因此還需要使用System.Diagnostics中的Process類

using (Process p = Process.GetCurrentProcess())
{
    p.PriorityClass = ProcessPriorityClass.High;
}

ProcessPriorityClass.High是一個略低於最高優先順序Realtime的級別。將一個進程的優先順序設置為Realtime是通知操作系統,我們絕不希望該進程將 CPU 時間出讓給其它進程。
如果你的程式誤入一個死迴圈,會發現甚至是操作系統也被鎖住了,就只好去按電源按鈕了o(>_<)o 正是由於這一原因,High 通常是實時程式的最好選擇。

什麼是進程退出?

進程退出一般出現在以下幾種情況:

  • 正常退出,進程執行完任務。

  • 錯誤退出,進程遇到不可繼續運行的錯誤(發生異常未捕獲導致程式退出)

  • 被操作系統終止,進程本身有問題,比如進程企圖訪問不屬於自己的記憶體地址

  • 被其它進程終止,比如通過資源管理器我們可以選擇終止掉某個進程

以上只有前兩種情況是進程自願退出的,因此,總體上可以分為三類:進程自願退出,操作系統終止進程以及進程終止進程。

main()執行結束時會自動隱式調用exit(),windows下叫ExitProcess。中止整個程式的執行,把控制返還給操作系統,並返回一個整數值,通常0表示正常終止,非0表示異常終止,這個值將會返回給操作系統。

windows中通過任務管理器,linux中通過kill去殺掉一個進程,其資源是否會釋放?

會。進程的特征之一就是動態性,其生存周期就是產生到消亡。當發生進程終止後,調用進程終止原語,從PCB匯流排中將其刪除,將PCB結構歸還給系統,釋放該進程的資源給其父進程或者操作系統。

但不完全會。如果用戶強行終止了.NET 進程,所有線程都會被當作後臺線程一般丟棄,有的資源沒來得及釋放,需要等待一段時間

Process類有以下兩種方法:

  • CloseMainWindow:向主視窗消息迴圈發送wm_quit消息以請求關閉進程,這使程式有機會重新調用其子視窗和內核對象。
  • Kill:強制終止進程,就像在任務管理器中終止進程一樣。

我們可以使用visual studio組件:記憶體分析器 分析發現幾乎在所有情況下,kill速度更快,但通過檢查實時記憶體圖可以發現其“根引用”和“實例引用”釋放的記憶體更少。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ##SpringBoot集成JWT(極簡版) ###在WebConfig配置類中設置介面統一首碼 import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.anno ...
  • 這一篇問文章主要介紹元組的相關知識。 元組:不可修改的序列 與列表一樣,元組也是序列,唯一的差別在於元組是不能修改的(同樣的,字元串也不能修改)。 元組的語法很簡單。 >>> >>> 1, 2, 3 (1, 2, 3) >>> (1, 2, 3) (1, 2, 3) >>> >>> () () >> ...
  • 大家好,我是三友~~ 之前有小伙伴私信我說看源碼的時候感覺源碼很難,不知道該怎麼看,其實這有部分原因是因為沒有弄懂一些源碼實現的套路,也就是設計模式,所以本文我就總結了9種在源碼中非常常見的設計模式,併列舉了很多源碼的實現例子,希望對你看源碼和日常工作中有所幫助。 單例模式 單例模式是指一個類在一個 ...
  • 目錄 一.glut 簡介 二.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 特效 零基礎 OpenGL ES 學習路線推薦 : ...
  • 線程池無論是工作還是面試都是必備的技能,但是很多人對於線程池的實現原理卻一知半解,並不瞭解線程池內部的工作原理,今天一燈就帶大家一塊剖析線程池底層實現原理。 ...
  • aspnetcore上傳圖片也就是上傳文件有兩種方式,一種是通過form-data,一種是binary。 先介紹第一種form-data: 該方式需要顯示指定一個IFormFile類型,該組件會動態通過打開一個windows視窗選擇文件 及圖片。 postman演示如上,代碼如下: [HttpPos ...
  • 一:背景 1.講故事 前幾天 B 站上有位朋友讓我從高級調試的角度來解讀下 .NET7 新出來的 AOT,畢竟這東西是新的,所以這一篇我就簡單摸索一下。 二:AOT 的幾個問題 1. 如何在 .NET7 中開啟 AOT 功能 在 .NET7 中開啟 AOT 非常方便,先來段測試代碼。 interna ...
  • 前言 拋開死鎖不談,只聊性能問題,儘管鎖總能粗暴的滿足同步需求,但一旦存在競爭關係,意味著一定會有線程被阻塞,競爭越激烈,被阻塞的線程越多,上下文切換次數越多,調度成本越大,顯然在高併發的場景下會損害性能。在高併發高性能且要求線程安全的述求下,無鎖構造(非阻塞構造)閃亮登場。 參考文檔: C# - ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...