C#多线程赛跑实例

        结合上篇《多线程的基础》,这次我们写一个多线程的赛跑实例,内容很简单:超人和蜘蛛侠赛跑,因为超人飞的比蜘蛛侠跳的快,为了公平,我们让蜘蛛侠跑的长度小点,裁判负责宣布比赛的开始和结束。

class MultiThread
    {
        //定义两个线程,分别为超人和蜘蛛侠
        private static Thread SuperMan;
        private static Thread SpiderMan;
        //程序入口,比赛开始
        static void Main(string[] args)
        {
            //初始化数据
            InitData();
            //裁判吹哨,开始赛跑
            JudgeWork();
        }

        /// <summary>
        /// 初始化超人和蜘蛛侠的线程和姓名
        /// </summary>
        private static void InitData()
        {
            SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
            SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
            SuperMan.Name = "SuperMan";
            SpiderMan.Name = "SpiderMan";

        }
        /// <summary>
        /// 裁判开始比赛,最后宣布胜者
        /// </summary>
        private static void JudgeWork()
        {
            Console.WriteLine("{0}   PK   {1}", SuperMan.Name, SpiderMan.Name);
            Console.WriteLine("比赛即将开始,请各位做好准备!");
            Console.WriteLine("预备!");
            Console.Read();
            //Superman起跑
            Console.WriteLine("回车枪响,Superman开始起跑!");
            Console.Beep(654, 1200);
            SuperMan.Start(500);
            //Monster起跑
            Console.WriteLine("回车枪响,SpiderMan开始起跑!");
            SpiderMan.Start(200);
            SuperMan.Join();
            SpiderMan.Join();
            //宣布赛跑结果
            Console.WriteLine("我宣布比赛结束");
            //程序暂停12秒
            Thread.Sleep(12000);
        }
        /// <summary>
        /// 赛跑的过程
        /// </summary>
        /// <param name="obj">赛跑参数</param>
        private static void RunnerWork(Object obj)
        {
            int length = Int32.Parse(obj.ToString());
            Thread CurrentThread = Thread.CurrentThread;
            string CurThreadName = CurrentThread.Name;
            int speed;
            //超人速度为20
            if (CurThreadName == SuperMan.Name)
            {
                speed = 50;
            }
            //蜘蛛侠速度为20
            else if (CurThreadName == SpiderMan.Name)
            {
                speed = 20;
            }
            //如果不可控线程进入,采用以下速度
            else
            {
                speed = 1;
            }
            Console.WriteLine("{0},开始起跑…………", CurThreadName);
            for (int count = speed; count <= length; count += speed)
            {
                Thread.Sleep(1000);
                Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
            }
            Console.WriteLine("{0},到达终点!了咧欢迎……", CurThreadName);
        }
    }
        运行结果:

        C#多线程赛跑实例_第1张图片

            比赛刚刚开始,裁判即宣布结束,这不符合常理。仔细分析可以发现,程序可控制的进程一共有三个,即裁判、超人和蜘蛛侠,三个进程相互独立同时进行,所以裁判宣布比赛开始后即按照它的线程继续宣布结束。
        我们可以这样:在裁判宣布比赛开始后,让蜘蛛侠和超人的线程执行完毕再执行裁判进程:

//防止裁判的主进程先结束,让超人和蜘蛛侠的进程先执行完毕
SuperMan.Join();
SpiderMan.Join();
Console.WriteLine("我宣布比赛结束");
        这次的执行结果为:

        C#多线程赛跑实例_第2张图片

        赛跑结束,裁判才宣布比赛结束,但是还有问题,裁判总得宣布谁跑赢了吧,台底下这么多粉丝等着呢?这个我们可以用变量的方式保存署名,达到宣布谁为冠军的功能。
        为了展示同步异步读写问题,我们让超人赛跑中去拯救世界,然后回来继续比赛;先到达终点的人,自己花时间找粉笔,然后在黑板上署名,其他人看到黑板上有名字就不能再写,裁判宣布署名的人为胜者。

class MultiThread3
{

    //署名用的黑板
    static string NameBoard = "";
    //定义两个线程,分别为超人和蜘蛛侠
    private static Thread SuperMan;
    private static Thread SpiderMan;
    //程序入口,比赛开始
    static void Main(string[] args)
    {
        //初始化数据
        InitData();
        //裁判吹哨,开始赛跑
        JudgeWork();
    }

    /// <summary>
    /// 初始化超人和蜘蛛侠的线程和姓名
    /// </summary>
    private static void InitData()
    {
        SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
        SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
        SuperMan.Name = "SuperMan";
        SpiderMan.Name = "SpiderMan";

    }
    /// <summary>
    /// 裁判开始比赛,最后宣布胜者
    /// </summary>
    private static void JudgeWork()
    {
        Console.WriteLine("{0}   PK   {1}", SuperMan.Name, SpiderMan.Name);
        Console.WriteLine("比赛即将开始,请各位做好准备!");
        Console.WriteLine("预备!");
        Console.Read();
        //Superman起跑
        Console.WriteLine("回车枪响,SuperMan开始起跑!");
        Console.Beep(654, 1200);
        SuperMan.Start(500);
        //Monster起跑
        Console.WriteLine("回车枪响,SpiderMan开始起跑!");
        SpiderMan.Start(300);
        //防止裁判的主进程先结束,让超人和蜘蛛侠的进程先执行完毕
        SuperMan.Join();
        SpiderMan.Join();
        //宣布赛跑结果
        AnnounceWinner();
        //程序暂停12秒
        Thread.Sleep(12000);
    }
    /// <summary>
    /// 赛跑的过程
    /// </summary>
    /// <param name="obj">赛跑参数</param>
    private static void RunnerWork(Object obj)
    {
        int length = Int32.Parse(obj.ToString());
        Thread CurrentThread = Thread.CurrentThread;
        string CurThreadName = CurrentThread.Name;
        int speed;
        //超人速度为20
        if (CurThreadName == SuperMan.Name)
        {
            speed = 50;
        }
        //蜘蛛侠速度为20
        else if (CurThreadName == SpiderMan.Name)
        {
            speed = 20;
        }
        //如果不可控线程进入,采用以下速度
        else
        {
            speed = 1;
        }
        Console.WriteLine("{0},开始起跑…………", CurThreadName);
        for (int count = speed; count <= length; count += speed)
        {
            Thread.Sleep(1000);
            Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
            //超人跑到一半,去拯救世界
            if (count == length / 2)
            {
                if (CurThreadName == SuperMan.Name)
                {
                    Console.WriteLine("世界末日来临,超人去拯救世界……");
                    string waitInfo = "..";
                    //超人拯救世界过程
                    for (int j = 0; j <= 10; j++)
                    {
                        Console.WriteLine("超人拯救世界中" + waitInfo);
                        waitInfo += "..";
                        Thread.Sleep(1000);
                    } 
                    Console.WriteLine("超人去拯救世界归来,继续赛跑……");
                }
            }
        }
        Console.WriteLine("{0},到达终点!乐咧欢迎……", CurThreadName);
        WriteName(CurThreadName);
    }

    /// <summary>
    /// 跑到重点线后,选手自己在黑板上署名
    /// </summary> 
    /// <param name="name">选手姓名</param>
    private static void WriteName(string name)
    {
        //黑板上没名字,才可以署自己的名字
        if (NameBoard.Length == 0)
        {
            Console.WriteLine("{0}去找粉笔了……", name);
            //找粉笔花费的时间
            Thread.Sleep(9000);
            Console.WriteLine("{0}拿着粉笔回来了,开始署名……", name);
            NameBoard = name;
            Console.WriteLine("{0}署完名后,开心的离开了……", name);
        }
        //黑板上有署名时不能再署名
        else
        {
            Console.WriteLine("{0}发现已经署名,桑心的离开了……", name);
        }
    }
    /// <summary>
    /// 宣布比赛结果
    /// </summary>
    private static void AnnounceWinner()
    {
        Console.WriteLine("我是裁判,我宣布这次比赛的冠军是{0}", NameBoard);
    }
}
        运行结果:

        C#多线程赛跑实例_第3张图片

        C#多线程赛跑实例_第4张图片
        可以看到明明是SuperMan还在拯救地球时,SpiderMan已经到达终点,而裁判宣布的冠军却是SuperMan。仔细分析一下程序即可知道:虽然SpiderMan先到达终点,并且先发现黑板是空的,但是在SpiderMan寻找粉笔的过程中,SuperMan到达终点,并且也发现黑板是空的,于是两人都写上了自己的名字,但是因为后者的会覆盖前者的,所以胜利者成了SuperMan,整个过程如下图所示:

        C#多线程赛跑实例_第5张图片

        问题出现的原因在于,SpiderMan到达以后看到黑板,SuperMan仍然看到黑板,即这个黑板对于两个人都是可写的,后者会覆盖前者的内容,这种方式为异步写。
        怎么克服这个问题?可以使用Lock锁住临界区代码,如下:

        //定义一个对象类型的objLock
        private static object objLock = new object();
        /// <summary>
        /// 跑到重点线后,选手自己在黑板上署名
        /// </summary> 
        /// <param name="name">选手姓名</param>
        private static void WriteName(string name)
        {
            //采用异步读方式,筛选掉已经看到署名的线程,提高效率
            //黑板上没名字,才可以署自己的名字
            if (NameBoard.Length == 0)
            {
                //因为上面为异步读,所以可能多个线程可以进入到这一步
                lock (objLock)
                {
                    //同步读方式
                    if (NameBoard.Length == 0)
                    {
                        Console.WriteLine("{0}去找粉笔了……", name);
                        //找粉笔花费的时间
                        Thread.Sleep(9000);
                        Console.WriteLine("{0}拿着粉笔回来了,开始署名……", name);
                        NameBoard = name;
                        Console.WriteLine("{0}署完名后,开心地离开了……", name);
                    }
                    //黑板上有署名时不能再署名
                    else
                    {
                        Console.WriteLine("{0}发现已经署名,桑心地离开了……", name);
                    }
                }
            }
        }

             需要注意的是,锁住的内容(非临界区代码)必须是共享型的引用型数据,因为如果是局部变量针对一个线程锁不锁对其它线程意义不大;采用引用数据类型,可以保证每个线程锁住内容都指向同一个地址。

        为了直观显示,没有抽象出超人、蜘蛛侠和裁判的类,以上就是一个简单的多线程应用实例,当然这是多线程的冰山一角,更多的还有待在以后开发中实践,这次争取在考试系统使用多线程优化抽题和判分等功能。

你可能感兴趣的:(C#多线程赛跑实例)