zoukankan      html  css  js  c++  java
  • 线程系列01,前台线程,后台线程,线程同步

    在控制台应用程序集中,Main方法开始的是一个线程。如果要再创建线程,需要用到System.Threading这个命名空间。

     

    □ 线程是怎样工作的?


    CLR维护着一个叫"thread scheduler"的机制,这个机制与操作系统交互。"thread scheduler"保证所有的线程有合适的执行时间,那些等待或被阻塞的线程不会占有CPU时间。
    在单处理器计算机上,"thread scheduler"在几十毫秒的时间内切换当前线程的执行。在多处理器计算机上,"thread scheduler"一方面也会扮演在单处理器上的角色,此外,还会同时把不同的线程交给不同的CPU处理。值得注意的是:使用多线程并不是总是好的。

     

    □ 线程和进程

     

    一台运行的电脑上可能会有多个进程,一个运行的进程可能会有多个线程。进程之间是完全隔离的,但在同一应用程序中不同线程可以共享数据。线程之间可以协同工作,比如一个后台线程用来获取数据,当获取到数据之后,另一个线程用来显示数据。

     

    □ 多线程的应用场景

     

    ○ 保持一个更快响应的UI界面:让UI线程只处理鼠标和键盘触发事件,其它线程处理其它事件。
    ○ 有效利用CPU:一个线程被阻塞,该线程就不会占有CPU资源。
    ○ 并行编程:执行一个比较耗时的任务时,可以分摊给多个线程。
    ○ 投机执行:让一个有可能被执行的线程先提前执行;让不同的线程用不同的算法,最先得出结果的胜出。
    ○ 请求被同步处理:无论是在客户端还是在服务端,可以用多线程同时处理多个并发请求。

     

    □ 创建第一个线程

    using System;
    
    using System.Threading;
    
    namespace ConsoleApplication4
    
    {
    
        class Program
    
        {
    
            static void Main(string[] args)
    
            {
    
                var thread = new Thread(DoSth);
    
                thread.Start();
    
            }
    
            static void DoSth()
    
            {
    
                Console.WriteLine("我来自另外一个线程");
    
            }
    
        }
    
    }
    

     

    实际上,new Thread(ThreadStart del)中的形参是委托类型:

    public delegate void ThreadStart();

     

    既然是委托,那在声明委托的时候,同样可以使用委托的构造函数:

    var thread = new Thread(new ThreadStart (DoSth))

    如果DoSth方法带参数,可以这样写:

    var thread = new Thread(new ParameterizedThreadStart(DoSth));

     

    □ 前台线程和后台线程

    新建的线程在默认情况下是前台线程,可以通过把IsBackground属性设置为true,把线程定义为后台线程,一旦定义成后台线程,只要前台线程结束,无论后台线程是否结束,应用程序进程结束。

    using System;
    
    using System.Threading;
    
    namespace ConsoleApplication4
    
    {
    
        class Program
    
        {
    
            static void Main(string[] args)
    
            {
    
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    
                var thread = new Thread(DoSth);
    
                thread.IsBackground = true;
    
                thread.Start(1);
    
                Console.WriteLine("离开主线程");
    
            }
    
            static void DoSth(object threadId)
    
            {
    
                Console.WriteLine("我来自另外一个线程" + threadId);
    
            }
    
        }
    
    }
    

    ○ ManagedThreadId属性,托管线程Id,在进程内唯一,与操作系统的线程Id不是一回事。
    ○ Start方法可以带参数,参数将被传递到线程方法
    ○ IsBackground属性,设置线程是否为后台线程

     

    默认情况下,创建的线程都是前台线程,只要有一个前台线程在运行,运用程序就不会停止;如果有些线程的是后台线程,当所有的前台线程运行完毕,应用程序就会停止,所有的后台线程也必须同时被终止。

    □ 线程同步

    ※ 线程不同步的问题

    假设主程序中有一个静态变量,在主程序的方法内无限循环,每次让该静态变量自增1。

    如果把该方法交给一个线程。

        class Program
    
        {
    
            private static int count = 0;
    
            static void Main(string[] args)
    
            {
    
                var t1 = new Thread(AddCount);
    
                t1.Start();
    
            }
    
            static void AddCount()
    
            {
    
                while (true)
    
                {
    
                    int temp = count;
    
                    Thread.Sleep(1000);
    
                    count = temp + 1;
    
                    Console.WriteLine("我的托管线程ID为:" + Thread.CurrentThread.ManagedThreadId + " 目前count的值为:" + count);
    
                    Thread.Sleep(1000);
    
                }
    
            }
    
        }
    

    运行良好,显示的count值是连续递增1。

     

    如果把该方法交给2个线程。

        class Program
    
        {
    
            private static int count = 0;
    
            static void Main(string[] args)
    
            {
    
                var t1 = new Thread(AddCount);
    
                var t2 = new Thread(AddCount);
    
                t1.Start();
    
                t2.Start();
    
            }
    
            ......
    
        }
    

    我们发现,count的值不是递增。也就是说,count的值没有做到同步。

    1

    →进入线程1,temp=0,线程1开始sleep
    →进入线程2,temp=0,线程2开始sleep
    →线程1"醒来",让count=1,显示count值为1,又sleep
    →线程2"醒来",temp还是为0,所以count还是为1,显示count值为1,又sleep
    →如此循环
    这里的问题是:本想让count一直递增,但线程1和线程2没有适时同步。如何解决呢?

     

    ※ 让线程同步

    使用lock语句块,可以让2个线程同步,让每次只有一个线程进入程序执行的某个部分。

        class Program
    
        {
    
            private static int count = 0;
    
            static object o = new object();
    
            static void Main(string[] args)
    
            {
    
                var t1 = new Thread(AddCount);
    
                var t2 = new Thread(AddCount);
    
                t1.Start();
    
                t2.Start();
    
            }
    
            static void AddCount()
    
            {
    
                while (true)
    
                {
    
                    lock (o)
    
                    {
    
                        int temp = count;
    
                        Thread.Sleep(1000);
    
                        count = temp + 1;
    
                        Console.WriteLine("我的托管线程ID为:" + Thread.CurrentThread.ManagedThreadId + " 目前count的值为:" + count);
    
                    }            
    
                    Thread.Sleep(1000);
    
                }
    
            }
    
        }
    

    2

     

    总结:
    ○ 如果允许一个主线程结束,其它线程不管执行情况如何都结束,就把其它线程设置为后台线程。
    ○ lock语句块能保证线程同步

     

    线程系列包括:

    线程系列01,前台线程,后台线程,线程同步

    线程系列02,多个线程同时处理一个耗时较长的任务以节省时间

    线程系列03,多线程共享数据,多线程不共享数据

    线程系列04,传递数据给线程,线程命名,线程异常处理,线程池

    线程系列05,手动结束线程

    线程系列06,通过CLR代码查看线程池及其线程

    线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步

    线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

    线程系列09,线程的等待、通知,以及手动控制线程数量

    线程系列10,无需显式调用线程的情形

  • 相关阅读:
    22 组合电路中的竞争--冒险
    21 典型的组合电路模块(2)
    vhdl和verilog的区别
    17 TTL电路系列(2)
    树莓派Pico
    ESP8266/ESP32自动下载电路原理分析
    CH340芯片
    26. 删除排序数组中的重复项
    25. K 个一组翻转链表
    23. 合并K个排序链表
  • 原文地址:https://www.cnblogs.com/darrenji/p/3979641.html
Copyright © 2011-2022 走看看