C#用两个线程交替打印1-100的五种方法

 "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开发跳槽”,关注可获得海量面试题

五种效果如下:

C#用两个线程交替打印1-100的五种方法

以上五种方法各有优缺点,没有一种方法是绝对最优的,取决于应用场景和要求。以下是对五种方法的简单比较和说明:
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