• Thread


    先来看几个基本概念(纯属个人见解,可能不准确):

    进程:程序运行时,占用的全部运行资源的总和。

    线程:线程由线程调度程序在内部管理,CLR通常将这一功能委托给操作系统。线程也可以有自己的计算资源,是程序执行流的最小单位。任何的操作都是由线程来完成的。

    每个线程都在操作系统的进程内执行,而操作系统进程提供了程序运行的独立环境。

    多线程:多核cpu协同工作,多个执行流同时运行,是用资源换时间。(单核cpu,不存在所谓的多线程)。

    单线程应用:在进程的独立环境中只跑一个线程,所以该线程拥有独立权。

    多线程应用:单个进程中会跑多个线程,它们会共享当前的执行环境(尤其是内存)。

    在单核计算机上,操作系统必须为每个线程分配“时间片”来模拟并发。而在多核或多处理器计算机上,多个线程可以真正的并行执行。(可能会受到计算机上其他活动进程的竞争)。

    win10上的时间片(使用微软官方小工具测得):

    Thread

      Thread的对象是非线程池中的线程,有自己的生命周期(有创建和销毁的过程),所以不可以被重复利用(一个操作中,不会出现二个相同Id的线程)。

    Thread的常见属性:

    • 线程一旦开始执行,IsAlive就是true,线程结束就变成false。
    • 线程结束的条件是:线程构造函数传入的委托结束了执行。
    • 线程一旦结束,就无法再重启。
    • 每个线程都有一个Name属性,通常用于调试,线程的Name属性只能设置一次,以后更改会抛出异常。
    • 静态的Thread.CurrentThread属性,会返回当前线程。

    Thread的常见用法:

    join

    调用join方法可以等待另一个线程结束。

    private void button5_Click(object sender, EventArgs e) {     
    Console.WriteLine($"===============Method start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}==================="); //开启一个线程,构造方法可重载两种委托,一个是无参无返回值,一个是带参无返回值 Thread thread = new Thread(a => DoSomeThing("Thread")); //当前线程状态 Console.WriteLine($"thread's state is {thread.ThreadState},thread's priority is {thread.Priority} ,thread is alived :{thread.IsAlive},thread is background:{thread.IsBackground},thread is pool threads: {thread.IsThreadPoolThread}"); //告知操作系统,当前线程可以被执行了。 thread.Start(); //阻塞当前执行线程,等待此thread线程实例执行完成。无返回值 thread.Join(); //最大等待的时间是5秒(不管是否执行完成,不再等待),返回一个bool值,如果是true,表示执行完成并终止。如果是false,表示已到指定事件但未执行完成。 thread.Join(5000); Console.WriteLine($"===============Method end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},,Thread ID is {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}==================="); }
    private void DoSomeThing(string name) { Console.WriteLine($"do some thing start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}"); long result = 0; for (long i = 0; i < 10000 * 10000; i++) { result += i; } Console.WriteLine($"do some thing end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}"); }

    Sleep

    Thread.Sleep()会暂停当前线程,,并等待一段时间。其实,Thread.Sleep只是放弃时间片的剩余时间,让系统重新调度并选择一个合适的线程。

    在没有其他活动线程的情况下,使用Thread.Sleep(0)还是会选上自身,即连任,系统不会对其做上下文切换。

    static void Main(string[] args)
    {
        Stopwatch stopwatch=new Stopwatch();
        stopwatch.Start();
        Thread.Sleep(0);
        stopwatch.Stop();
        System.Console.WriteLine(stopwatch.ElapsedMilliseconds); //返回0
    }

    而Thread.Sleep(大于0)却让当前线程沉睡了,即使只有1ms也是沉睡了,也就是说当前线程放弃下次的竞选,所以不能连任,系统上下文必然发生切换。

    阻塞

    如果线程的执行由于某种原因导致暂停,那么就认为该线程被阻塞了。例如在Sleep或者Join等待其他线程结束。被阻塞的线程会立即将其处理器的时间片让给其他线程,从此就不再消耗CPU时间片时间。可以通过ThreadState这个属性来判断线程是否处于被阻塞状态。

    下面是ThreadState的所有值(官方截图):

    ThreadState是一个flags枚举,通过按位的形式可以合并数据的选项。

    bool isBlocked = (thread.ThreadState & ThreadState.WaitSleepJoin) != 0;

     本地独立(Local)

    CLR为每个线程分配自己的内存栈(Statck),以便本地变量保持独立。

    static void Main(string[] args)
    {
        //在新线程上调用Test()
        new Thread(Test).Start();
        //在主线程上调用Test()
        Test();
    }
    static void Test()
    {
        //i 是本地的局部变量,在每个线程的内存栈上,都会创建i独立的副本
        for (int i = 0; i < 5; i++)
        {
            System.Console.Write("o");
        }
    }
    //结果会输出十个o

     共享(Shared)

    如果多个线程都引用了同一个对象的实例,那么它们就共享了数据。

    class Program
    {
        bool flag;
        static void Main(string[] args)
        {
            //创建一个实例
            Program program = new Program();
            //分别在新线程上和主线程上调用同一实例的Test()方法
            new Thread(program.Test).Start();
            program.Test();
            //输出一个FLAG
        }
        void Test()
        {
            if (!flag)
            {
                flag = true;
                System.Console.WriteLine("FLAG");
            }
        }
    }

    被Lambda表达式或匿名委托所捕获的本地变量,会被编译器转换为字段(field),所以也会被共享。

    class Program
    {
        static void Main(string[] args)
        {
            //此局部变量会被编译器转换为所在类的字段,进行处理,所以会被共享。
            bool flag = false;
            ThreadStart threadStart = () =>
            {
                if (!flag)
                {
                    flag = true;
                    System.Console.WriteLine("FLAG");
                }
            };
            new Thread(threadStart).Start();
            threadStart();
        }

     静态字段(field)也会在线程间共享数据。

    class Program
    {
        //静态字段在同一应用域下的所有线程中被共享
        static  bool flag = false;
        static void Main(string[] args)
        {
            new Thread(Test).Start();
            Test();
        }
        static void Test()
        {
            if(!flag){
                flag=true;
                System.Console.WriteLine("FLAG");
            }
        }
    }

    线程安全 

    由于线程中数据可以共享,可能会引发线程安全(Thread Safety)问题(上面三个例子都有线程安全问题,应尽可能的避免使用共享状态)。

    在读取和写入共享数据的时候,通过使用一个互斥锁(exclusive lock),就可以解决上面三个例子的线程安全问题。锁可以基于任何引用类型对象。使用lock语句来加锁,当两个线程同时竞争一个锁的时候,一个线程会等待或阻塞,直到锁变成可用状态。

    class Program
    {
        //静态字段在同一应用域下的所有线程中被共享
        static bool flag = false;
        static readonly object locker = new object();
        static void Main(string[] args)
        {
            new Thread(Test).Start();
            Test();
        }
        static void Test()
        {
            lock (locker)
            {
                if (!flag)
                {
                    System.Console.WriteLine("FLAG");
                    Thread.Sleep(1000);
                    flag = true;
                }
            }
        }
    }

    注意:防止lock使用不当引起死锁,最好不要lock public的东西,例如:

    1lockthis2lock(“string”)
    3locktypeofint))

    向线程传递数据

    往线程的启动方法里传递数据:

    1、使用Thread的重载构造方法和Thread.Start()的重载方法来传递数据。(只能接收object类型的参数,是C# 3.0之前的用法)

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(Print).Start("asdf");
        }
        static void Print(object? message)
        {
            System.Console.WriteLine(message);
        }
    }

    2、使用Lambda表达式,在里面使用参数调用方法。

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(()=>Print("Hello World!")).Start();
        }
        static void Print(string message)
        {
            System.Console.WriteLine(message);
        }
    }

    使用Lambda表达式可以很简单的给Thread传递参数。但是线程开始后,可能会不小心修改了被捕获的变量,这要多加注意。

    static void Main(string[] args)
    {
        //i是一个瞬时的临时变量
        for (int i = 0; i < 10; i++)
        {
            //每个线程对Console.Write(i)的调用,传递的是当时的i值。
            new Thread(()=>Console.Write(i)).Start();
        }
    }

    解决办法:

    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            //每次循环都有一个临时变量记录i的值。
            int temp=i;
            new Thread(()=>Console.Write(temp)).Start();
        }
        //但是输出顺序依然无法保证
    }

     前台和后台线程

    默认情况下,手动创建的线程就是前台线程。只要有前台线程在运行,那么应用程序就会一直处于活动状态。

     thread 默认是前台线程,启动后一定要完成任务的,即使程序关掉(进程退出)也要执行完。可以把thread 指定为后台线程,随着进程的退出而终止。

    //false,默认是前台线程,启动后一定要完成任务的,即使程序关掉(进程退出)也要执行完。
    Console.WriteLine(thread.IsBackground); 
    thread.IsBackground = true;//指定为后台线程。(随着进程的退出而退出)
    static void Main(string[] args)
    {
        Thread thread = new Thread(() =>
          {
              Console.ReadLine();
          });
        if (args.Length > 0)
            thread.IsBackground = true;
        thread.Start();
        /*
            如果运行时不传递参数,thread为前台线程,程序会等待输入而不会退出。
        如果在运行时传递参数(dotnet run XXXX),thread变成后台线程,会随着进程的结束而终止线程。
         */
    }

    注意:线程的前台、后台状态与它的优先级无关。

    Thread的回调用法:

    Thread没有像Framework中的delegate的回调用法,如果需要回调得自动动手改造:

    private void CallBack(Action action, Action calback)
    {
        Thread thread = new Thread(() => { action(); calback(); });
        thread.Start();
    }
    //无参无返回值
    CallBack(() => Console.WriteLine("好吗?"), () => Console.WriteLine("好的!"));
    private Func<T> CallBackReturn<T>(Func<T> func)
    {
        T t = default(T);
        Thread thread = new Thread(() =>
        {
            t = func();
        });
        thread.Start();
        return () =>
        {
            thread.Join();
            return t;
        };
    }
    //带返回值得用法
    Func<int> func = CallBackReturn<int>(() => DateTime.Now.Second);
    Console.WriteLine("线程未阻塞");
    int result = func.Invoke();
    Console.WriteLine("result:" + result);

    ThreadPool 线程池

  • 相关阅读:
    Pytorch 入门之Siamese网络
    Pytorch 之 backward
    非极大值抑制(NMS)
    Apriori 算法python实现
    canny 算子python实现
    转载
    ubuntu 安装lightgbm
    OneHotEncoder独热编码和 LabelEncoder标签编码
    Adroid—— DVM
    PHP——做服务
  • 原文地址:https://www.cnblogs.com/lizhitong/p/14422691.html
Copyright © 2020-2023  润新知