线程简单入门

Posted Nemo_XP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程简单入门相关的知识,希望对你有一定的参考价值。

操作系统Windows资源有1.内存,2.CPU,3.存储硬盘,4,带宽.
操作系统在分配资源的最小单位是进程。粗略的说,一个应用程序exe,就是一个进程,如qq,vs,爱奇艺等。每个进程所分配的资源都是相互独立的,不能相互调用。
所以进程,是windows系统的一个基本概念,它包含着一个运行程序所需要的的资源,进程之间是相互独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方式),一个进程运行失败也不会影响其他进程的运行,windows系统就是利用进程把工作划分为多个独立的区域,进程可以理解为一个程序的边界

早起的操作系统CPU都是单核的,其中cpu的其中一个核心,同一时间只能执行一个指令。但是对于操作系统来说,同一时间处理多任务才是人们需要的,为了达到这个效果,操作系统做了一些处理,因为cpu执行的速度非常快,单核每秒可执行30亿次(如intel core i5 2430M cpu时钟 2.4 GHz ,双核心,那么理论运算速度应为 22.4G (G= 10001000*1000=10亿)也就是每秒运算48亿次左右。) 。操作系统就把资源分配给多个你打开的进程,而对于cpu资源拿单核为例,只需要在极微小的时间内,可能是千分之一秒去执行第一个进程,然后再切换第二个进程,这样就会给人感觉计算机同时在执行很多任务。
CPU在切换执行进程的时候,因为一个进程占用资源非常多,所以可能在切换进程的时候需要保存之前进展资源状态,读取show当前进程的状态,占用的时间就多,就会给人明显的卡顿状态,为了解决这个问题,windows把每个进程,分为一个一个的小份(线程,执行流),windows切换占用资源的时候只处理每个进程的某个线程的资源即可,所以就会很快了。
所以一个线程就是一个进程里面的代码执行流。每个线程都要指向一个方法体,方法执行完之后,线程就释放。一个进程可以包含一个或多个线程。
线程语法:

    class Program
    {
        static void Main(string[] args)
        {
            ThreadStart ts = new ThreadStart(NewFunc);// ThreadStart参数是一个方法名,即委托
            Thread thread = new Thread(ts);// 线程Thread,需要传入一个ThreadStart对象
            thread.Start();// 告诉操作系统,线程已经准备好,可以执行了
        }

        static void NewFunc()
        {
            Console.WriteLine("我是一个新线程");
        }
    }

这么写会觉得麻烦,我们对ThreadStart按F12,发现它就是一个内置的无参无返回值的委托而已,所以我们使用另一种简化的语法

static void Main(string[] args)
        {
            // 参数可直接传入一个无参无返回值的委托,这里使用了lambda
            Thread thread = new Thread(() =>
            {
                Console.WriteLine("我是一个新线程");
            });
            thread.Start();// 告诉操作系统,线程已经准备好,可以执行了
        }

那如果想写一个带参数的委托怎么写呢,其实net还内置了一个ParameterizedThreadStart来实现有参的委托
ParameterizedThreadStart pts = new ParameterizedThreadStart(NewThread);// 参数为object,此委托只有一个参数,如果需要多个参数,要自己使用数组,集合,元祖实现

我们代码(thread.Start();)已经告诉操作系统,线程已经准备好,可以执行了,可是什么时候执行还要看操作系统的心情,毕竟操作系统可能有几千个线程要执行,得排队一个一个来雨露均沾呢,如下图的电脑目前就有3625个线程要执行。
有人可能会又这样一个疑问,既然我总共即那么多内核,每秒执行的次数固定,线程固定,那我对某个进程多创建线程和少创建线程,执行完成的时间应该是一致的猜对,毕竟最终执行的次数是一定的,只不过是依次切换执行两个线程和一块执行两个线程的区别。其实答案还是,抢占cpu资源,假设cpu要执行2个进程,每个进程5个线程,我给其中一个进程多加1个线程,那就多了一次抢占cpu执行线程的机会,相比5个线程的时候自然会更快了。
在这里插入图片描述
关于后台线程IsBackground,是一个很重要的点,线程分为前台线程和后台线程,一般主线程如控制台应用程序的Main方法,就是前台线程,只要有前台线程没有关闭,程序就不会关闭。会造成一些问题,所以我们如无必要的需求,一般设置IsBackground为True,默认为后台线程,只要前台主线程一结束关闭,哪怕后台线程还在跑也要立马关闭释放资源(即不会阻塞进程的退出)。例如如下代码:

       // Main:net程序入口,clr一开始就会创建一个默认的主线程(前台线程),指向Main方法.
        static void Main(string[] args)
        {
            Thread thread = new Thread(NewThread);
            thread.IsBackground = true;// 设置为false的时候,因为新线程是无线循环的,所以是关不掉程序的,改成true,只要程序主线程Main,后台线程都会立马关闭
            thread.Start();

            Console.ReadKey();
        }

        static void NewThread()
        {
            while (true)
            {
                Thread.Sleep(1000);
                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            }
        }

线程类的其他常用成员
Thread thread = Thread.CurrentThread;// 当前线程
thread.Priority = ThreadPriority.Highest;// 设置本进程内线程执行优先级
int id = thread.ManagedThreadId;// 线程的id标识
string name = thread.Name;// 线程的名称
thread.Start();// 启动线程
thread.Join(10000);// 执行目前线程代码的线程等待thread线程执行完成,参数代表最多等待thread线程10000ms,如果期间thread执行完毕,立即执行接下来的代码,否则不管thread能不能执行完都只等待10000ms
thread.Abort();// 不得已才用,直接终结线程,并释放资源.会造成一定的问题

常见问题:跨线程资源调用
使用Winform的时候,我们可能常常会遇到一个问题,在UI的事件方法执行时候,如果我们运行了一个需要执行很久的方法,此时UI是被占用的,造成的结果就是这个时候只有执行完成了这个方法,我们才可以拖动窗体或者点其他按钮。如下例子

private void Form1_Shown(object sender, EventArgs e)
        {
            // 此时拖动窗体是卡死的状态
            for (int i = 0; i < 1000; i++)
            {
                this.Text = DateTime.Now.ToString("HH:ss:mm");
                Thread.Sleep(1000);
            }
        }

所以此时,我们就需要新建一个线程来做处理此长时间的不影响整体代码运行方法。
在这里插入图片描述
于是我们就遇到这个非常常见的问题,这是因为this.Text是UI线程里的资源,thread是另一个线程,它不能跨线程UI线程来访问其他线程内资源。
如何处理:
在UI线程内(一般构造方法或者Load)使用:Control.CheckForIllegalCrossThreadCalls = false;即可。它是要求不去检查跨线程调用UI资源。
我们极不建议这样解决,所以有了以下的
跨线程资源调用的解决方案
使用Invoke调用,

private void Form1_Shown(object sender, EventArgs e)
        {
            Thread thread = new Thread(NewThread);
            // Control.CheckForIllegalCrossThreadCalls = false;
            thread.Start();
        }

        private void NewThread()
        {
            for (int i = 0; i < 1000; i++)
            {
                if (this.InvokeRequired)// 如果是别的线程调用此(this)UI线程内控件
                {
                     演示带参写法
                    //this.Invoke(new Action<string>((s) =>
                    //{
                    //    this.Text = s; 
                    //}), DateTime.Now.ToString("HH:mm:ss"));

                    // 就让资源主人(拥有者)去做处理。 这里非常类似窗体传值相互调用用委托那个原理
                    this.Invoke(new Action(() =>
                    {
                        this.Text = DateTime.Now.ToString("HH:mm:ss");
                        //Thread.Sleep(1000);// 这个一定要写到委托的外层,不然UI窗体还是会卡死的
                    }));

                    Thread.Sleep(1000);
                }

            }
        }

线程池
语法演示:

        static void Main(string[] args)
        {
            // 参数委托为线程池线程要执行的回调方法。回调方法:在执行完此行的时候立即调用的方法
            ThreadPool.QueueUserWorkItem(q=> {
                Console.WriteLine(q);
            },"sssss参数内容");

            Console.ReadKey();
        }

线程池原理:
线程池里有很多的线程,当我们使用线程池帮我们执行某项任务的时候,通过线程池代码初始化一个工作任务给线程池,线程池就会找一个空闲的线程去执行任务。执行完成后,此线程不会立即销毁。而是回到线程池里,等待其他线程池请求重复使用。
线程池提高了线程的利用率,非常适合工作任务非常小,而且有需要使用单独的线程来解决。
手动线程:
启动一个线程就要开辟一个内存空间,里面放线程的基本信息,最小的线程也要有1M的内存。线程切换消耗资源,cpu在切换线程的时候,需要把当前线程执行的状态保持在寄存器里面,线程创建非常消耗资源,线程创建非常慢,占用大量的内存空间。

线程池里的线程的优势:
1线程池的线程本身都是后台线程,不用重新赋值变量IsBackground 。
2线程可以进行重用。
3.线程池相比反复手动线程时效率高很多。(因为重复利用,避免创建释放占用大量的内存和时间)

什么时候用线程池?什么时候用手动创建线程?
1.能用线程池的就用线程池。不考虑处理顺序的话。
2.我们想手动关闭线程的话,就用手动创建(Abort,Join)
3.我们需要对线程优先级做设置,就手动创建
4.如果执行的线程执行时间特别长。

线程池内部原理
在这里插入图片描述
队列

ThreadPool.GetMaxThreads(out numMax,out runNumMax);//线程池当前可设置最大线程数和当前设置的线程数。GetMinThreads同理。

异步委托
普通调用委托的时候,是Main线程在调用委托,同步自上而下的执行委托。
而beginInvoke则是利用线程池里的线程调用的委托,即异步后台委托

        static void Main(string[] args)
        {
            Func<int, int, int> delcFunc = (a, b) => a + b;
            int sum = delcFunc(3, 4);// 手动调用委托,同步,同Main方法同一个线程,自上而下执行完上一个才会执行下一个
            delcFunc.BeginInvoke(3, 4, null, null);// 利用线程池里的线程调用的委托,即异步后台委托,此是不要求获取返回值直接异步调用委托

            // 异步委托一般直接调用,不等待返回值,如果需要获取返回值,代码如下
            IAsyncResult result = delcFunc.BeginInvoke(3, 4, null, null);
            int sum1 = delcFunc.EndInvoke(result);
            // EndInvok方法会阻塞当前线程,知道一步委托指向完成之后,才继续往下执行(效果相当于普通调用委托)
        }

使用beginInvoke如何获得返回值
1.同步调用(阻塞线程)

        static void Main(string[] args)
        {
            Func<int, int, int> delcFunc = (a, b) => a + b;
            
            IAsyncResult result = delcFunc.BeginInvoke(3, 4, null, null);
            int sum1 = delcFunc.EndInvoke(result);
            // EndInvok方法会阻塞当前线程,知道一步委托指向完成之后,才继续往下执行(效果相当于普通调用委托)
        }

2.异步获取返回值:通过回调函数

    class Program
    {
        static void Main(string[] args)
        {
            // 执行步骤
            // 1.Main线程执行的代码
            //......

            // 2.1.异步调用线程
            Func<int, int, int> delcFunc = (a, b) => a + b;
            delcFunc.BeginInvoke(3, 4, CallBackFunc, null);

            // 2.2.Main线程执行的代码(不管2.1有没有执行完都同时执行2.2)
            //......

        }

        // 3.执行回调函数:(2.1执行完成后执行此方法)
        static void CallBackFunc(IAsyncResult ar)
        {
            // 拿到异步执行的结果
            AsyncResult result = (AsyncResult)ar;
            var dele = (Func<int, int, int>)result.AsyncDelegate;// 得到原来的委托
            int sum = dele.EndInvoke(result);
            Console.WriteLine(sum);// 拿到结果要做的事儿
        }
    }

执行顺序

以上是关于线程简单入门的主要内容,如果未能解决你的问题,请参考以下文章

推荐net开发cad入门阅读代码片段

python threading超线程使用简单范例的代码

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

java基础入门-多线程同步浅析-以银行转账为样例

Kotlin入门(29)任务Runnable

Java的多线程 简单入门