• Threads(线程)(二)


    前一章我们提到了同步异步,多线程;在开始今天的文章之前,先来总结一下上一篇文章的内容,多线程的优点。

    多线程有哪些优点呢,在这里通过代码依次来总结。

    异步多线程的三大特点

    1)同步方法卡界面,原因是主线程被占用;异步方法不卡界面,原因是计算交给了别的线程,主线程空闲

    首先创建winfrom程序,建一个普通方法,然后异步、同步调用

            /// <summary>
            /// 执行动作:耗时而已
            /// </summary>
            private void TestThread(string threadName)
            {
                Console.WriteLine("TestThread Start  Name={2}当前线程的id:{0},当前时间为{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName);
                long sum = 0;
                for (int i = 1; i < 999999999; i++)
                {
                    sum += i;
                }
                //Thread.Sleep(1000);
                Console.WriteLine("TestThread End  Name={2}当前线程的id:{0},当前时间为{1},计算结果{3}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName, sum);
            }
           
    

    同步调用

     private void btnSync_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("**********************btnSync_Click Start 主线程id {0}****************************************", Threa            d.CurrentThread.ManagedThreadId);
    
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnSync_Click_{0}", i);
                    TestThread(name);
                }
    
                watch.Stop();
                Console.WriteLine("**********************btnSync_Click   End 主线程id {0} {1}*******************************", Thread.Cur            rentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    异步调用

       private void btnAsync_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnAsync_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnAsync_Click_{0}", i);
                    Action act = () => this.TestThread(name);               
                    act.BeginInvoke(null, null);              
                }
    
                watch.Stop();
                Console.WriteLine("**********************btnAsync_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    通过运行,你会发现当同步调用时,整个winfrom界面是不能动的,你一动程序运行就会停止。

    这是因为当使用同步方法时,主线程在运算,而移动winform界面的动作也是需要主线程操作。这就会形成卡顿或动不了的情况。

    而异步调用就不会出现这种情况。上一章也讲过,异步调用时,方法体的运算实在其他线程中运行,而主线程会继续往下运行,很快就结束了运算,这个时候主线程是空闲的,所以就不会出现这总情况。

    2) 同步方法慢,原因是只有一个线程计算;异步方法快,原因是多个线程同时计算,但是更消耗资源,不宜太多

    通过运行结果也可以分析,同步方法主线程运行时间有十几秒,而异步方法也就六七秒。通过对CPU运行分析,就更能够理解了。

    当不运行是,我么的CPU利用率保持在20%-30%之间。

    但我们调用同步方法时,利用率到达了50%-60%之间

    当我们调用异步方法时,CPU爆表,达到100%

    所以说异步调用不能在程序中无限使用,不知道在哪里看过,据不官方的报道,程序的线程数在电脑cpu核数4倍左右会达到最佳。

    简单来说异步多线程就是资源来换取时间。 

    3)异步多线程是无序的,启动顺序不确定、执行时间不确定、结束时间不确定

    通过分析前面的运行结果可以看出,线程开始运行顺序是10、12、13、11、14,而结束是12、10、13、11、14。而且方法体运行时按理说应该是在循环体中从0-4依次开始,而异步开始的顺序是0、2、1、3、4,这些都是不确定的。

    *************************************************************************************************************************************************************************************

    前面总结了这么多,进入今天的主题,Thread(线程)。

    其实线程(Thread)和前面的异步调用差不太多,关于概念的东西在前一篇文章中有阐述,这里就不说明了。那么我们怎么声明线程呢?

         private void btnThread_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnThread_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
                List<Thread> threadList = new List<Thread>();
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnThread_Click_{0}", i);
                    ThreadStart method = () => this.TestThread(name);
                    Thread thread = new Thread(method);
                
                    thread.Start();
                  
                }
    
                watch.Stop();
                Console.WriteLine("**********************btnThread_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    首先。就跟我们声明一个委托一样声明一个ThreadStart对象,然后把对象当变量的形式添加到线程中,最后开始线程。

    注意:这样开始线程 默认前台线程,就是说程序退出后,计算任务会继续进行。

    那么怎么改为后台线程呢,一般来说程序都退出了,就不需要进行计算任务了。这个时候就需要加一个属性

    thread.IsBackground = true;// 后台线程:程序退出,计算立即结束
    

    如果我们需要主线程等到其他线程结束后在结束,应该怎么做呢,也就是说希望主线程可以等待一段时间。

         private void btnThread_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnThread_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
                List<Thread> threadList = new List<Thread>();
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnThread_Click_{0}", i);
                    ThreadStart method = () => this.TestThread(name);
                    Thread thread = new Thread(method);//1 默认前台线程:程序退出后,计算任务会继续
                    thread.IsBackground = true;//2 后台线程:程序退出,计算立即结束
                    thread.Start();
                threadList.Add(thread);
                }
    
                foreach (Thread thread in threadList)
                {
                    thread.Join();
                }
    
                watch.Stop();
                Console.WriteLine("**********************btnThread_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    我们需要声明一个Thread类型的List。没开始一个线程,就把线程加到list中,然后遍历,把每个线程Join(),因为这一句话是主线程运行的,也就是说主线程在等待其他线程结束后再接着运行。

    Thread(线程)利于线程的回收,有点浪费资源,后来框架推出了池的概念。

    也就是ThreadPool。就相当于添加了一个管理员,把线程都放在池子里,便于线程的管理。

      private void btnThreadPool_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnThreadPool_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnThreadPool_Click_{0}", i);
                    WaitCallback method = t => this.TestThread(t.ToString());
                    ThreadPool.QueueUserWorkItem(method, name);
                }
          
                watch.Stop();
                Console.WriteLine("**********************btnThreadPool_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    我们只需要ThreadPool.QueueUserWorkItem(),接受一个WaitCallback类型的变量,和一个参数,这个参数会作用于WaitCallback。这就完成了线程的使用,简单了许多。。。但是如果我们也需要线程等待呢,该怎么做。

            private void btnThreadPool_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnThreadPool_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
              
                ManualResetEvent mre = new ManualResetEvent(false);
                WaitCallback method = t =>
                    {
                        this.TestThread(t.ToString());
                        mre.Set();//打开
                    };
                ThreadPool.QueueUserWorkItem(method, "TestManualResetEvent");
    
                Console.WriteLine("我们来干点别的。。。。");
                Console.WriteLine("我们来干点别的。。。。");
                Console.WriteLine("我们来干点别的。。。。");
                Console.WriteLine("我们来干点别的。。。。");
                mre.WaitOne();
    
             
                watch.Stop();
                Console.WriteLine("**********************btnThreadPool_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
    

    只需要声明ManualResetEvent 并且设置为false;然后在委托中set打开一下,就行了。相应的ReSet就是关闭。关闭后如果不在打开就不会执行后面的内容

      ManualResetEvent mre = new ManualResetEvent(false);
                new Action(() =>
                {
                    Thread.Sleep(5000);
                    Console.WriteLine("委托的异步调用");
                    mre.Set();//打开
                }).BeginInvoke(null, null);
    
                mre.WaitOne();
                Console.WriteLine("12345");
                mre.Reset();//关闭
                new Action(() =>
                {
                    Thread.Sleep(5000);
                    Console.WriteLine("委托的异步调用2");
                    // mre.Set();//打开
                }).BeginInvoke(null, null);
                mre.WaitOne();
                Console.WriteLine("23456");
    

    后面的打印23456就会停止打印,会一直处于等待状态。

    ******************************************************************************************************************************************************************************************************************************

    后来在.net3.0时代推出了task概念。task,在这里讲几种创建方式,这里只是稍微涉及一下,在后面的章节中会细讲,

    1)通过TaskFactory ,直接StartNew开始并创建一个线程。

      private void btnTask_Click(object sender, EventArgs e)
            {
                Stopwatch watch = new Stopwatch();
                watch.Start();
                Console.WriteLine();
                Console.WriteLine("***********************btnTask_Click Start 主线程id {0}**********************************", Thread.CurrentThread.ManagedThreadId);
    
                TaskFactory taskFactory = new TaskFactory();
    
                for (int i = 0; i < 5; i++)
                {
                    string name = string.Format("btnAsync_Click_{0}", i);
                    Action act = () => this.TestThread(name);
                    taskFactory.StartNew(act);              
                }
    
                watch.Stop();
                Console.WriteLine("**********************btnTask_Click   End 主线程id {0} {1}************************************", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
                Console.WriteLine();
            }
           
    

    2)建Task 对象,然后Start。

    Task task = new Task(act);
    task.Start();
    

    3)直接对Task对象Run(),这样也可以对线程创建并开始;

    Task task = Task.Run(act);
    

    当然Task的创建不知这么几种,这里只是稍微涉及,在后面的章节会细讲^-^

    未完待续。。。。。。。。。。

  • 相关阅读:
    Tensorflow io demo (待)
    tf.Dataset
    tf.estimator
    并发队列
    Callable的Future模式
    hadoop之HDFS介绍
    线程池
    并发工具类
    并发编程
    初学hadoop之hadoop集群搭建
  • 原文地址:https://www.cnblogs.com/xima/p/7211592.html
Copyright © 2020-2023  润新知