C#小案例-->交替吃苹果线程同步案例

「鸿蒙心迹」“2025・领航者闯关记“主题征文活动 10w+人浏览 405人参与

方法一:

编写代码实现切换逻辑
using System;
using System.Threading;

namespace 交替吃苹果
{
    class Program
    {
        // 共享资源:表示当前剩余的苹果数量
        // 使用 private static 修饰,因为它需要被多个线程访问,且属于类级别的变量
        private static int _appleCount = 10;

        static void Main(string[] args)
        {
            Console.WriteLine("游戏开始:张三和李四开始交替吃苹果!");
            Console.WriteLine("-------------------------------------\n");

            // 1. 创建两个 AutoResetEvent 对象,用于线程间的信号通信
            // AutoResetEvent 是一个线程同步事件,它有两种状态:有信号(Signaled)和无信号(Non-Signaled)。
            // 线程可以调用 WaitOne() 方法来等待信号,如果事件是有信号状态,WaitOne() 会立即返回,并将事件重置为无信号状态。
            // 如果事件是无信号状态,WaitOne() 会阻塞线程,直到另一个线程调用 Set() 方法将其设置为有信号状态。

            // lisiEvent: 初始状态为有信号(true),这意味着李四线程可以先执行
            AutoResetEvent lisiEvent = new AutoResetEvent(initialState: true);

            // zhangsanEvent: 初始状态为无信号(false),这意味着张三线程需要等待信号
            AutoResetEvent zhangsanEvent = new AutoResetEvent(initialState: false);

            // 2. 创建并定义李四的线程
            // 使用 Lambda 表达式来定义线程要执行的代码块
            Thread lisiThread = new Thread(() =>
            {
                // 这是一个无限循环,线程会一直执行,直到苹果吃完
                while (true)
                {
                    // 等待 lisiEvent 信号。
                    // - 如果信号是“有”(初始状态),线程会继续执行,并将 lisiEvent 自动重置为“无”。
                    // - 如果信号是“无”,线程会在这里阻塞(暂停),直到有其他线程调用 lisiEvent.Set()。
                    lisiEvent.WaitOne();

                    // 3. 临界区:访问共享资源前的检查
                    // 由于两个线程都可能修改 _appleCount,所以在操作前必须再次检查。
                    // 这是为了防止在一个线程吃完最后一个苹果后,另一个线程才被唤醒并尝试再次吃苹果。
                    if (_appleCount <= 0)
                    {
                        // 如果苹果已经吃完,当前线程(李四)需要唤醒另一个线程(张三),
                        // 否则张三线程会永远阻塞在 zhangsanEvent.WaitOne(),导致程序无法正常结束。
                        zhangsanEvent.Set();
                        break; // 跳出循环,结束当前线程
                    }

                    // 4. 执行“吃苹果”的业务逻辑
                    Console.WriteLine("李四正在吃苹果...");
                    _appleCount--; // 苹果数量减一(共享资源被修改)
                    Console.WriteLine($"李四吃完一个苹果。剩余苹果: {_appleCount} 个\n");

                    // 5. 通知张三线程可以开始吃苹果了
                    // 将 zhangsanEvent 设置为“有”信号。
                    // 这会唤醒正在等待 zhangsanEvent 信号的张三线程。
                    zhangsanEvent.Set();
                }
                // 线程执行完毕后会自动终止
            });
            // 设置线程的名称,方便在调试时识别
            lisiThread.Name = "李四线程";

            // 6. 创建并定义张三的线程
            // 逻辑与李四线程对称
            Thread zhangsanThread = new Thread(() =>
            {
                while (true)
                {
                    // 等待 zhangsanEvent 信号。
                    // 初始状态为“无”,所以张三线程会在这里等待,直到李四吃完一个苹果并调用 zhangsanEvent.Set()。
                    zhangsanEvent.WaitOne();

                    // 同样,在访问共享资源前检查苹果是否还有剩余
                    if (_appleCount <= 0)
                    {
                        // 唤醒李四线程,确保它也能正常结束
                        lisiEvent.Set();
                        break; // 跳出循环,结束当前线程
                    }

                    // 执行“吃苹果”的业务逻辑
                    Console.WriteLine("张三正在吃苹果...");
                    _appleCount--; // 苹果数量减一(共享资源被修改)
                    Console.WriteLine($"张三吃完一个苹果。剩余苹果: {_appleCount} 个\n");

                    // 通知李四线程可以开始吃下一个苹果了
                    lisiEvent.Set();
                }
                // 线程执行完毕后会自动终止
            });
            zhangsanThread.Name = "张三线程";

            // 7. 启动两个线程
            // 此时,两个线程会开始执行它们各自的 while 循环中的代码。
            // 由于 lisiEvent 初始为有信号,李四线程会先执行。
            lisiThread.Start();
            zhangsanThread.Start();

            // 8. 让主线程等待子线程执行完毕
            // Join() 方法会阻塞当前的主线程,直到调用它的线程(lisiThread 和 zhangsanThread)执行完毕。
            // 这可以防止主线程在子线程完成任务前就打印“程序结束”并退出。
            lisiThread.Join();
            zhangsanThread.Join();

            // 9. 所有子线程执行完毕,游戏结束
            Console.WriteLine("-------------------------------------");
            Console.WriteLine("游戏结束:苹果已全部吃完!");
        }
    }
}
测试演示

方法二:

编写代码实现切换逻辑
namespace _02_交替吃苹果线程同步案例
{
    internal class Program
    {

        // 定义一个共享资源 苹果数量
        private static int AppleNumber = 10;
        private static Thread threadA;
        private static Thread threadB;
        static void Main(string[] args)
        {
            threadA = new Thread(EatApple);
            threadB = new Thread(EatApple);
            threadA.Name = "张三";
            threadB.Name = "李四";
            threadA.Start();
            threadB.Start();
        }

        // 定义一个常量
        private static readonly object locker = new object();

        private static void EatApple()
        {
            while (true)
            {
                // 添加Lock锁
                lock (locker)
                {
                    Console.WriteLine(Thread.CurrentThread.Name + "正在吃苹果");
                    Thread.Sleep(3000);
                    // 苹果的数量减少一个
                    AppleNumber--;
                    Console.WriteLine(Thread.CurrentThread.Name+"吃完了,还剩"+AppleNumber+"个苹果\n");
                    // 当苹果的数量小于等于1的时候
                    if (AppleNumber <= 1)
                    {
                        break;
                    }
                }
            }
        }
    }
}
测试演示

希望对大家有所帮助。感谢大家的关注和点赞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值