"C#用兩個線程交替列印1-100的五種方法"是.NET工程師面試多線程常考的試題之一,主要考察對C#語法和對多線程的熟悉程度。本文將用5種方法實現這個面試題。 方法1:使用Mutex或lock 這種方法涉及使用Mutex或lock對象來同步兩個線程。其中一個線程負責列印偶數,另一個線程負責列印奇數 ...
"C#用兩個線程交替列印1-100的五種方法"是.NET工程師面試多線程常考的試題之一,主要考察對C#語法和對多線程的熟悉程度。本文將用5種方法實現這個面試題。
方法1:使用Mutex或lock
這種方法涉及使用Mutex或lock對象來同步兩個線程。其中一個線程負責列印偶數,另一個線程負責列印奇數。線程在執行任務之前會鎖定共用的Mutex或lock對象,以確保每個線程執行任務時只有一個線程能夠訪問共用資源。代碼如下:
class Program { static Mutex mutex = new Mutex(); static int count = 1; static void Main(string[] args) { Thread t1 = new Thread(PrintOddNumbers); Thread t2 = new Thread(PrintEvenNumbers); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadLine(); } static void PrintOddNumbers() { while (count <= 100) { mutex.WaitOne(); if (count % 2 == 1) { Console.WriteLine("Thread 1: " + count); count++; } mutex.ReleaseMutex(); } } static void PrintEvenNumbers() { while (count <= 100) { mutex.WaitOne(); if (count % 2 == 0) { Console.WriteLine("Thread 2: " + count); count++; } mutex.ReleaseMutex(); } } }
方法2:使用AutoResetEvent
AutoResetEvent是一種線程同步機制,允許一個線程等待另一個線程發出信號來繼續執行。其中一個線程負責列印奇數,另一個線程負責列印偶數。當一個線程完成列印任務時,它發出信號以喚醒另一個線程來繼續執行。
class Program { static AutoResetEvent oddEvent = new AutoResetEvent(false); static AutoResetEvent evenEvent = new AutoResetEvent(false); static int count = 1; static void Main(string[] args) { Thread t1 = new Thread(PrintOddNumbers); Thread t2 = new Thread(PrintEvenNumbers); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadLine(); } static void PrintOddNumbers() { while (count <= 100) { if (count % 2 == 1) { Console.WriteLine("Thread 1: " + count); count++; evenEvent.Set(); oddEvent.WaitOne(); } } } static void PrintEvenNumbers() { while (count <= 100) { if (count % 2 == 0) { Console.WriteLine("Thread 2: " + count); count++; oddEvent.Set(); evenEvent.WaitOne(); } } } //歡迎關註公眾號“DOTNET開發跳槽”,關註可獲得海量面試題
方法3:使用Monitor
Monitor是C#中的一種同步機制,類似於Mutex。其中一個線程負責列印奇數,另一個線程負責列印偶數。線程在執行任務之前會鎖定共用的Monitor對象,以確保每個線程執行任務時只有一個線程能夠訪問共用資源。
class Program { static object lockObj = new object(); static int count = 1; static void Main(string[] args) { Thread t1 = new Thread(PrintOddNumbers); Thread t2 = new Thread(PrintEvenNumbers); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadLine(); } static void PrintOddNumbers() { while (count <= 100) { lock (lockObj) { if (count % 2 == 1) { Console.WriteLine("Thread 1: " + count); count++; } } } } static void PrintEvenNumbers() { while (count <= 100) { lock (lockObj) { if (count % 2 == 0) { Console.WriteLine("Thread 2: " + count); count++; } } } } }
方法4:使用信號量Semaphore
Semaphore是一種同步機制,允許多個線程同時訪問共用資源。其中一個線程負責列印奇數,另一個線程負責列印偶數。線程在執行任務之前會等待信號量,以確保每個線程只有在獲得信號量之後才能訪問共用資源。
class Program { static Semaphore semaphore = new Semaphore(1, 1); static int count = 1; static void Main(string[] args) { Thread t1 = new Thread(PrintOddNumbers); Thread t2 = new Thread(PrintEvenNumbers); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadLine(); } static void PrintOddNumbers() { //註意 這裡是99,否則會出現101 while (count <= 99) { semaphore.WaitOne(); if (count % 2 == 1) { Console.WriteLine("Thread 1: " + count); count++; } semaphore.Release(); } } static void PrintEvenNumbers() { while (count <= 100) { semaphore.WaitOne(); if (count % 2 == 0) { Console.WriteLine("Thread 2: " + count); count++; } semaphore.Release(); } } }
方法5:使用Task和async/await
在C#中,使用Task和async/await關鍵字可以輕鬆地在兩個線程之間切換執行。其中一個線程負責列印奇數,另一個線程負責列印偶數。線程在執行任務之前使用async/await等待非同步任務完成,以確保每個線程只在非同步任務完成後才訪問共用資源。
class Program { static int count = 1; static void Main(string[] args) { Task.Run(PrintOddNumbers); // 這裡改成這個也可以 // var thread1 = new Thread(PrintOddNumbers); Task.Run(PrintEvenNumbers); Console.ReadLine(); } //如果用Thread改成同步方法 static async Task PrintOddNumbers() { while (count <= 100) { if (count % 2 == 1) { Console.WriteLine("Thread 1: " + count); count++; //如果用Thread這裡改成 Thread.Sleep(1); await Task.Delay(1); } } } static async Task PrintEvenNumbers() { while (count <= 100) { if (count % 2 == 0) { Console.WriteLine("Thread 2: " + count); count++; await Task.Delay(1); } } } //歡迎關註公眾號“DOTNET開發跳槽”,關註可獲得海量面試題
五種效果如下:
以上五種方法各有優缺點,沒有一種方法是絕對最優的,取決於應用場景和要求。以下是對五種方法的簡單比較和說明:1、使用ManualResetEventWaitHandle:這種方法在實現上較為簡單,但是由於線程必須互斥地訪問共用資源,因此會導致性能瓶頸。此外,使用ManualResetEventWaitHandle需要頻繁調用WaitOne和Set方法,可能會降低應用程式的響應能力。2、使用AutoResetEventWaitHandle:這種方法在實現上比較簡單,而且使用AutoResetEventWaitHandle可以避免性能瓶頸問題。然而,它仍然需要頻繁調用WaitOne和Set方法,可能會降低應用程式的響應能力。3、使用鎖:使用鎖可以避免性能瓶頸問題,因為同一時間只有一個線程可以訪問共用資源。但是,鎖可能會導致線程死鎖和性能下降的問題,因此需要小心使用。4、使用信號量Semaphore:這種方法可以避免性能瓶頸問題,並允許多個線程同時訪問共用資源。Semaphore還可以設置多個許可證,以控制併發線程的數量。然而,使用Semaphore可能會使代碼變得更加複雜,因此需要小心使用。5、使用Task和async/await:這種方法可以避免性能瓶頸問題,並且使用Task和async/await可以使代碼更加簡潔易懂。但是,它可能會對記憶體和CPU產生額外的開銷,因為需要在任務之間頻繁地切換上下文。綜上所述,選擇哪種方法取決於應用程式的要求和程式員的個人偏好。如果應用程式需要更好的性能,則應該使用鎖或信號量;如果應用程式需要更簡潔易懂的代碼,則應該使用Task和async/await。
考察的知識點
1、C#編程語言和語法:實現多線程程式需要熟悉C#編程語言和語法,包括線程的創建和管理、共用資源的訪問和同步等方面的知識。2、多線程編程:多線程編程是指同時運行多個線程的編程模型,它可以提高應用程式的性能和響應能力。多線程編程需要考慮線程的同步、共用資源的訪問、線程間的通信等問題。3、線程同步機制:線程同步機制是指用於控制多個線程訪問共用資源的機制,常用的線程同步機制包括鎖、信號量、事件等。4、非同步編程:非同步編程是指不阻塞線程並且在完成任務後通知線程的編程模型,它可以提高應用程式的響應能力和性能。非同步編程需要熟悉非同步和await關鍵字、Task和Task<T>等類型、async和await方法等概念。參考:chatGPT