`

C#模拟超人和小怪兽赛跑——多线程学习之(二)牛刀小试

阅读更多

    在上一篇博客中对线程有了一个初步的概要框架,从这篇博客开始,从代码层面深入学习C#的多线程编程艺术……


    线程的重点在于线程的控制,这里使用C#创建控制台程序,模拟超人和小怪兽赛跑的故事,涉及到.NET平台,线程的创建、开启和线程控制中锁的使用,算是多线程学习的一道开胃小菜吧大笑


    故事的背景:


    一天,超人见到了小怪兽,说要进行一场跑步比赛,但是小怪兽(20m/s)说:“超人(50m/s)你跑的速度快,你要比我多跑一段路程”。超人想了想,我是超人,我怕谁?于是就答应道:“好吧,那我就让你200米”。就这样,两人一拍即合,于是乎,请来了一位教练。 可是教练比较懒,在比赛开始前,先宣布比赛规则:第一个跑到终点的选手,自己将名字写到终点的黑板上,然后又分别悄悄地跟超人和小怪兽叫到一边,小声的说:“等你跑完的时候通知我一声,然后我宣布比赛结束”。裁判一声枪响,两人开始冲锋,可是超人起初怎么能想到今年是2012,世界末日?!于是就在他跑到一半的时候,去拯救地球,回来后继续接着跑。小怪兽对超人全然不知,只顾低头跑步。但是在这场逐鹿汇中,究竟鹿死谁家……???


    程序模拟:


    分析上面的过程,我们需要创建超人、小怪兽这两个线程,来执行跑步的方法;定义一个静态的白板,用于记录第一个跑到终点的选手的姓名;裁判要等到两个线程都结束的时候才宣布比赛结束(线程的阻塞);超人跑到一半的时候拯救地球和第一个跑到终点的选手去找粉笔,然后记下自己的名字,都是模拟的线程中执行了其他的方法导致的阻塞或延时;系统中定义的白板变量是两个线程公用的数据,在程序中使用锁的方式达到异步读和同步写的目的。

1、定义超人和小怪兽的线程,并声明白板变量,并附带一把锁。

        //定义静态的超人和小怪兽的线程,可以让主线程识别——定义线程
        private static Thread superman;
        private static Thread master;
        //设计一个记录选手名单的白板
        private static string nameboard;
        //定义一个专门用于多线程的锁对象,是一种互斥锁。是引用类型的,引用的是对象的地址,这样不管其他的程序拿到多少个引用,最终还是同一个实际数据;不能是值类型,因为如果是值类型的时候,就是实际数据的副本,不同的对象使用的时候,是对象数据的副本,这样修改后数据就不一致了;引用类型,不管被引用了多少次,但是实例只有一个,一旦这个实例被上锁后,实例只可以被一个人抢占。
        private static object lockobj = new object();

2、程序模拟超人和小怪兽跑步的方法——runnerWork

        /// <summary>
        /// 赛跑的方法。是每一个选手公共工作的方法,返回值是空(Thread定义的委托决定的),传入object表示赛跑的路程(封装的是int类型的参数,必须是object类型,系统已经定义好的委托的类型决定的)
        /// obj封装赛跑的路程,设计一个拆装箱的过程 
        /// </summary>
        /// <param name="obj"></param>
        private static void  runnerWork( object obj )
        {
            int length = Int32.Parse(obj.ToString());

            #region 关于获取线程名的几种写法比较

            //最安全的获取线程名称的写法。但在赋值号中可能会被其他线程打断,这种可能性很小,另外我们还可以配合锁,来解决这个问题
            string currentname = Thread.CurrentThread.Name;

            ////这种写法是安全的,因为线程内部会保留线程的一个副本名称,线程与线程之间是相互隔离的,每一个线程会在内存中开辟自己的一段空间,所以,并不是线程越多越好。
            //Thread  curThread  = Thread.CurrentThread;
            //string strThreadName = curThread.Name;

            ////这种写法是不安全的,因为两行代码之间随时可能会有其他的线程插入
            //Thread curThread = Thread.CurrentThread;
            //string strThreadName = Thread.CurrentThread.Name;

            #endregion

            #region 给超人和小怪兽的速度定义一个数值            
            int speed ;
            if (currentname == superman.Name)
            {
                speed = 50;
            }
            //针对已知的线程使用else if,目的是为了提高线程程序的可扩展性,在多线程中尤为重要
            else if (currentname == master.Name)
            {

                speed = 20;
            }
            else
            {
                speed = 1;
            }
            #endregion

            #region 模拟跑步的过程

            Console.WriteLine("<" + currentname +">开始起跑……\n");
            
            for (int count = speed; count <= length; count += speed)
            {
                Thread.Sleep(1000);
                Console.WriteLine("<" + currentname +">跑到了第"+ count.ToString()+"米\n");

                #region 跑到一半的时候,超人去拯救地球
		 	
                if (count == length / 2)
                {
                    if (currentname == superman.Name)
                    {
                        Console.WriteLine("<" + currentname + ">开发现了一个地球危机,去拯救地球……");
                        //Thread.Sleep(20000);
                        string waitinfo = "..";
                        //模拟超人线程在执行赛跑的时候,被阻塞
                        for (int j = 1; j < 20; j += 2)
                        {
                            Console.WriteLine("<" + currentname + ">拯救地球中" + waitinfo);
                            waitinfo += "..";
                            Thread.Sleep(2000);

                        }

                            Console.WriteLine("<" + currentname + ">拯救地球归来,继续赛跑……");
                    }
                    else
                    { 
                        //什么也不做
                    }

                }                
                else
                { 
                    //什么也不做
                }
                #endregion
            }

            Console.WriteLine("<" + currentname + ">冲线!\n");

            #endregion

            //自主方式记下自己的名字
            writeName(currentname);


        }        

3、选手写自己名字的方法——writeName

        /// <summary>
        /// 自主的方式记下选手自己的名字
        /// </summary>
        /// <param name="name"></param>
        private static void writeName(string name)
        {
            if (nameboard.Length == 0)  //异步读,筛选掉那些没有机会写的线程,提高效率
           
            {
                lock (lockobj)
                {
                    //如果发现白班是空的,那么就记下名字,否则,失意的走开
                    if (nameboard.Length == 0)   //如果记名板是空的,那么就去写下自己的名字    //同步读 //筛选那些同时进入第一次的异步读时,名字板还是空的时候,有机会排队等锁的人,保证写入安全
                    {
                        Console.WriteLine("<" + name + "去找白粉笔……");
                        //Thread.Sleep(1000);                        
                        Thread.Sleep(9000);
                        Console.WriteLine("<" + name + "找到了白粉笔,开始写自己的名字……");
                        //任何实际发生之前都有可能被各种原因阻塞,此处用找粉笔模拟延时的过程
                        
                        nameboard = name;           //同步写


                        Console.WriteLine("<" + name + ">在锁住的房间中,得意洋洋的写下了自己的名字!");
                    }
                    else
                    {
                        Console.WriteLine("<" + name + ">在锁住的房间中很失望,失意的走开……");
                    }
                }
            }
            else
            {
                Console.WriteLine("<" + name + ">在房间外,没有机会进入房间,很失望,失意的走开……");
            }
        }

4、裁判宣布比赛结果的方法

        /// <summary>
        /// 裁判宣布胜利者
        /// </summary>
        private static void  announceResult( )
        {
            //裁判宣布比赛结束,等待所有子线程结束后才进行
            Console.WriteLine("我是裁判,我宣布比赛结束!");
            Console.WriteLine("本次比赛的优胜者为:" + nameboard);
        }

5、裁判的工作,开启两个分线程——judgeWork

        /// <summary>
        /// 裁判工作,作为赛跑的主线程
        /// </summary>
        private static void judgeWork()
        {
            Console.WriteLine("下面要准备开始比赛!");
            Console.Write("两位比赛选手分别为: ");
            Console.Write( superman.Name );
            Console.Write(" PK ");
            Console.Write(master.Name);
            Console.Write("\n");

            //驱动线程执行
            Console.Write("回车后开始赛跑:");
            Console.ReadLine();
            Console.Beep(456, 1200);
            Console.Write("发令枪响,选手起跑…… \n");

            int supermanlength = 500;
            int masterlength = 300;

            //向线程需要执行的方法传递参数————启动线程,这两个线程不一定是superman先启动,因为每个线程启动的时间,可能不一样,会受到运行环境还有内部的堆栈的影响
            //这里只是表示申请执行,还需要判断资源是否够用等
            //Console.WriteLine("回车后超人起跑!");
            ////这句话可以让主线程阻塞
            //Console.ReadLine();
            //事实上,屏幕上可能会先输出“回车后小怪兽起跑”,因为超人线程启动需要时间,这里面的主线程在继续执行。
            superman.Start(supermanlength);     //————启动线程         
            //Console.WriteLine("回车后小怪兽起跑!");
            //Console.ReadLine();
            master.Start(masterlength);         //————启动线程

            
            //Thread.Sleep(16000);
            //主线程等待superman线程
            Console.WriteLine("裁判与超人商量,等超人跑完后通知裁判!");
            Console.WriteLine("裁判与小怪兽商量,等小怪兽跑完后通知裁判!");

            //让主线程等待子线程全部跑完后再宣布比赛结束
            //主线程等待superman线程    
            superman.Join();
            //主线程等待master线程。这里的小怪兽线程与上面的超人的线程没有先后的关系。
            master.Join();
            
            //宣布获胜选手
            announceResult();

        }

6、为两个线程初始化数据——initDate

        /// <summary>
        /// 初始化线程数据:将runnerWork方法传给委托,当某个线程启动的时候,启动注入的事件
        /// 线程在执行的时候,是通过委托调用的方法;通过委托,我们可以为一个线程启动多个方法
        /// </summary>
        private static void initDate()
        {
            superman = new Thread(new ParameterizedThreadStart(runnerWork));
            master = new Thread(new ParameterizedThreadStart(runnerWork));
            //在一个线程中启动多个方法
            //ParameterizedThreadStart pstart;
            //pstart = new ParameterizedThreadStart(runnerWork);
            //pstart+=其他方法;
            //master = new Thread(pstart);
            superman.Name = "superman";
            master.Name = "小怪兽";
            nameboard = "";

        }

7、在控制台的main函数中,启动程序

        /// <summary>
        /// Main函数象征主线程,无特殊性,只是工作线程中的一个领跑者而已
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.Clear();
            //初始化数据,定义线程,封装函数
            initDate();
            //裁判开始工作,线程在这个方法的里面
            judgeWork();

        }



总结分析:

    创建线程的方法:

private static Thread superman;

   

     开启线程的方法:

superman.Start(supermanlength);

    阻塞主线程的方法:

    这里是阻塞的是裁判工作,只有当superman这个线程执行完后才可以继续执行裁判工作中这行代码后面的程序。另外,程序并不是先等superman这个线程执行完后再去等待master这个线程执行完的,这两个线程之间是互补干扰的。

superman.Join();
master.Join();

    线程中锁的使用:    

if (nameboard.Length == 0)  //异步读,筛选掉那些没有机会写的线程,提高效率
           
            {
                lock (lockobj)
                {
                    //如果发现白班是空的,那么就记下名字,否则,失意的走开
                    if (nameboard.Length == 0)   //如果记名板是空的,那么就去写下自己的名字    //同步读 //筛选那些同时进入第一次的异步读时,名字板还是空的时候,有机会排队等锁的人,保证写入安全
                    {
                        ………………模拟被各种原因阻塞线程的代码
                        nameboard = name;           //同步写


                        Console.WriteLine("<" + name + ">在锁住的房间中,得意洋洋的写下了自己的名字!");
                    }
                    else
                    {
                        Console.WriteLine("<" + name + ">在锁住的房间中很失望,失意的走开……");
                    }
                }
            }
            else
            {
                Console.WriteLine("<" + name + ">在房间外,没有机会进入房间,很失望,失意的走开……");
            }

     这里两次用到了if (nameboard.Length == 0)的判断。程序是这样的一个过程:刚开始的时候,有可能会有多个线程,发现白板是空的,于是就闯过了第一个异步读的判断,进到了房间,但是被lock住的白板相当于是一把锁,使用了lock之后就以为着,钥匙只有一把,假设有2个人,进到了房间,但是总有一个人抢到了钥匙,然后他就把白板独占,等到把自己的名字写好后,才释放资源,释放钥匙,但这个时候,闯到房间内的第二个人,看到白板上已经写上了别人的名字,那么他就不用花费力气再去占用白板资源了,只能直接走人……

    看下程序运行结果:


 


 




    通过上面这个小的程序,相信大家可以对线程有个初步的认识,尤其是对上篇博客中:“线程表示一种执行权限”这句话,有所理解了。

 

    我们通过线程就可以让跑步、写名字这些方法还有黑板这些方法、变量在程序中多个内存中执行、访问。因为cpu在工作的时候是按照时间片的方式执行的,就是在一个最小的单位时间内只执行一个程序,但是在不同的时间片之间可能会执行不同的程序,这样在宏观的时间概念上,就可以cpu是在同时进行着多个工作。

 

    如果我们的程序中有许多工作要做,就可以将工作分类,交给不同的线程来执行。比如说,一个线程专门负责,数据的输入、输出,另一个线程专门负责数据的计算,这样就可以让大工作量的计算单独执行,不用让其他的程序一直在等待。

 

    另外一些比较相似的工作,比如有1万块砖要搬,如果让一个人搬的话,会花上很长很长的时间,可是,如果使用100个人来共同完成这项工作,那么工作效率岂不是大大的提高了吗?但是也并不是人越多约好的,试想一个可以容下100个人自由行动的道路,如果硬是塞了500个人,这样一来,不但没有提高板砖的效率,反而还需要花费精力来协调多余的400人。所以,线程的数量不是越多越好。

 

    还有这么一个问题:如果有了多个人在搬砖的时候,可能就会考虑采用协作的方法了,如果我们事先在装车的时候就把转装到一个一个的框中,这样来板砖的人一看有装好的框,直接就可以提走了,这就是线程池的概念;另外,在砖厂还没有把框里砖装够数量的时候,负责拉砖的人,是不可以把这个框装车的,这样买砖的人会不干的,因为数量不够嘛,所以,这就涉及到了线程同步的关系。

 

    关于线程池和线程同步的方法,会在后续的博客中陆续贴出,这里只做一个过渡。


 

 

 

 


声明:上面的程序,并非本人原创,这里只是作为一个学习的材料。

0
1
分享到:
评论

相关推荐

    C# UDP多线程发送接收

    总结,C# UDP多线程通信是网络编程中的一个重要实践,它结合了UDP的高效传输特性和多线程的并发优势,适用于处理大量并发的网络请求。在实际项目中,开发者需要根据具体需求选择合适的同步机制,保证程序的稳定性和...

    龟兔赛跑 多线程 C# 动画显示

    在本文中,我们将深入探讨如何使用C#编程语言实现一个基于多线程的"龟兔赛跑"动画显示。这个项目结合了并发处理、图形用户界面(GUI)设计以及动画技术,展示了C#在实际应用中的强大功能。 首先,我们要理解多线程...

    c# 多线程 上位机

    总结,C#的多线程技术和模拟消息队列在上位机开发中起着至关重要的作用,它们能够优化性能,提高系统的响应速度,并为复杂任务的管理提供有效手段。理解并熟练运用这些技术,是成为一名合格的C#上位机开发者的关键。

    VisualStudio2008创建的龟兔赛跑动画多线程程序演示,C#源代码.

    总的来说,这个"龟兔赛跑动画多线程程序"是一个综合性的学习案例,涵盖了C#编程、多线程、UI交互、事件处理和同步控制等多个方面,对于理解和实践.NET框架下的并发编程十分有益。通过深入研究并理解这段源代码,...

    C#多线程互斥实例 多线程获取同一变量

    在这个"多线程互斥实例 多线程获取同一变量"的示例中,我们将探讨如何在多个线程中安全地访问共享资源,避免数据不一致性和竞态条件。 首先,我们需要理解多线程中的一些核心概念: 1. **线程**:线程是操作系统...

    C#的多线程机制探索

    在编程领域,尤其是在高性能和高并发的应用中,多线程技术是不可或缺的一部分。本文将深入探讨C#语言中的多线程机制,旨在帮助开发者更...通过不断实践和学习,开发者可以充分利用C#的多线程特性来提升程序的执行效率。

    WinForm C# 多线程编程并更新界面(UI)

    WinForm C# 多线程编程并更新界面(UI) 在 Windows 窗体应用程序中,多线程编程是非常重要的,这样可以提高应用程序的响应速度和用户体验。下面我们来讨论如何在 WinForm 中使用 C# 实现多线程编程并更新界面(UI...

    C#多线程排序例子

    在IT行业中,多线程是一种常见的编程技术,尤其在C#这样的高级编程语言中,它被广泛用于提高程序的执行效率和并发性。本示例“C#多线程排序例子”聚焦于如何利用多线程来提升排序操作的速度。 首先,让我们了解什么...

    C#多线程连接mysql,Access

    C#作为一种强大的.NET编程语言,提供了丰富的多线程支持,而MySQL和Access则分别是两种广泛应用的关系型数据库管理系统。本篇文章将深入探讨如何在C#中使用多线程技术连接这两种数据库,并对比它们在性能上的差异。 ...

    C# httpwebrequest 多线程下载类

    在C#编程中,开发人员经常需要...通过分析和学习这段代码,可以加深对`HttpWebRequest`和多线程下载的理解,进一步提升C#网络编程技能。在实际项目中,可以根据具体需求对其进行修改和扩展,以满足各种复杂的下载场景。

    C#多线程编程实战完整源码

    C#多线程编程是开发高效、响应迅速的软件应用的关键技术之一,尤其在现代计算环境中,多核处理器和并发处理的需求日益增加。本资源"《C#多线程编程实战》完整源码"提供了丰富的实例,适用于学习和实践C#中的多线程...

    大恒-双相机开发-C#-多线程-项目开源

    对于想要深入学习C#图像处理和多线程编程的开发者来说,这是一个非常有价值的开源项目。通过研究这个项目,我们可以了解到如何在实际应用中有效地结合各种技术,解决复杂的问题,提升系统的性能。

    C# 高效线程安全,解决多线程写txt日志类.zip

    总的来说,这个"C# 高效线程安全,解决多线程写txt日志类.zip"提供的方案对于多线程环境下的日志管理有着显著的价值,通过合理的同步机制和优化策略,实现了日志写入的安全性和高效性。开发者可以通过学习和使用这个...

    C#处理大容量数据,及多线程简单应用

    总结来说,处理大容量数据和多线程应用是C#开发中的核心技能。通过合理利用多线程、优化SQL查询和采用高效的数据库操作方法,我们可以构建出高性能、用户友好的应用程序。"CountDataSheet"这个文件名可能代表了对...

    C#基于TCP的Socket多线程通信(包含服务端和客户端)

    C#是一种广泛用于构建桌面和Web应用程序的编程语言,它提供了强大的网络编程能力,其中包括使用TCP Socket进行多线程通信。本篇文章将深入探讨C#中基于TCP的Socket多线程通信,包括服务端和客户端的实现。 TCP...

    VB.Net-C#多线程Thread-代理委托delegate编程

    最近收集的VB.Net-C#多线程Thread-代理委托delegate编程。文章列表: c#.net多线程同步.txt C#WebBrowser页面与WinForm交互技巧一.txt C#多线程编程-多细全.txt C#多线程编程简单实例.txt C#多线程窗体控件安全访问....

    WinForm C#多线程等待窗体

    在C#编程中,Windows Forms(WinForm)应用程序经常需要处理多线程操作,以便实现非阻塞用户界面。在标题“WinForm C#多线程等待窗体”中,我们探讨的关键点是如何在进行耗时操作时创建一个等待窗体,让用户知道程序...

    c#编写串口通讯代码 多线程实现

    以上知识点涵盖了C#中多线程串口通讯的基本原理和实践技巧,通过学习和掌握这些内容,开发者可以编写出高效、稳定的串口通信程序。在实际项目中,还需要根据具体需求和硬件环境调整和完善代码。

    C#多线程数据采集器

    在IT领域,多线程是一种常见的编程技术,特别是在高性能计算和并发处理中。C#作为.NET框架的主要语言,提供了丰富的支持来实现多线程应用程序。本文将深入探讨C#中的多线程,以及如何利用多线程进行数据采集。 1. *...

    c# 模拟多客户端程序,可模拟1000+客户端。

    综上所述,这个C#项目涉及了多线程编程、Socket通信、并发控制、异步编程等多个关键知识点,对于理解和实践网络编程、性能测试有着重要的学习价值。开发者需要对C#语法、网络编程原理和并发编程有深入理解,才能成功...

Global site tag (gtag.js) - Google Analytics