多线程详细介绍

什么是进程线程:咱们来看一下本身的任务管理器

  

这里的每一项都是一个进程,咱们的发布的每个应用程序都须要一个进程去运行,在一个进程内能够有多个线程去计算执行程序。咱们看下面的图片:html

  

咱们能够看一下进程和线程的数量,很明显能够看出,线程和进程的关系。咱们的每个操做都须要一个线程来执行,鼠标的点击就须要线程去响应咱们的操做。api

如今咱们不难理解,咱们一个应用程序就表明一个进程,想让咱们的程序高效的运行咱们就能够启用多个线程去执行了,固然采用多线程的话有好处也是有代价的,好处合理的利用计资源了,可是线程过多了,你的CPU利用率就加大了,也有可能致使电脑的卡死。安全

在咱们的程序中线程的表明就是:Thread本篇文章咱们就说一下线程。咱们先了解一下同步异步。多线程

同步:指的是在同一线程下执行,而且会等待结果执行完毕。框架

异步:再也不同一个线程下执行,而且执行得顺序不可控,不会等待执行结果完毕。异步

咱们先用委托来演示一下多线程,若是不怎么了解委托得能够看一下上一篇文章:性能

    DelegateMethod Method = () =>
            {
           Console.WriteLine($"个人线程ID是:{Thread.CurrentThread.ManagedThreadId}");
            };
            Method.BeginInvoke(null, null);
            Method.Invoke();

原本想用上面的代码去先简单的演示一下,谁知道NetCore 的程序集提供了,可是平台目前还不支持。 咱们能够私下使用NET 试一下。学习

使用Thread 演示spa

 

            Console.WriteLine($"*********************************我是同步方法 *******************************");

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"个人线程ID是:{Thread.CurrentThread.ManagedThreadId}");
            }
            Console.WriteLine($"*********************************同步方法结束 *******************************");

            Console.WriteLine($"*********************************我是异步方法 *******************************");
            for (int i = 0; i < 10; i++)
            {
                 Thread thread = new Thread(() => { Console.WriteLine($"个人线程ID是:{Thread.CurrentThread.ManagedThreadId}"); });
                 thread.Start();
            }
            Console.WriteLine($"*********************************异步方法结束 *******************************");
  
            Console.ReadKey();

执行结果:pwa

   

上面的代码执行结果中咱们能够看到:同步方法 是同一个线程来执行的,之上而下有序的执行,可是异步方法启动了多个线程去执行的,而且线程是无序的。看到这样的状况咱们就会知道若是我启动了不少线程线程用完以后也是有回收的,回收以后一样会分配的,也就是说同一个操做中线程的ID 可能会屡次出现的。

            Console.WriteLine($"*********************************异步启动线程数量计算  *******************************");
            List<int> ListInt = new List<int>();
            int Conut = 0;
            for (int i = 0; i < 2000; i++)
            {
                Thread thread = new Thread(() =>
                {
                    if (ListInt.Contains(Thread.CurrentThread.ManagedThreadId))
                    {
                        Conut++;
                        Console.WriteLine($"个人重复的线程个人 ID是:{Thread.CurrentThread.ManagedThreadId}  重复线程总数量:{Conut}");
                    }
                
                    ListInt.Add(Thread.CurrentThread.ManagedThreadId);
                });
                thread.Start();
            }
            Console.WriteLine($"*********************************异步启动线程数量计算结果:{ListInt.Count}  *******************************");

结果: 

  

上面的结果咱们能够看到:线程回收再利用。其实thread线程的回收就是咱们的GC来作的,这就是C# 的强大之处,自动帮助咱们回收了。须要注意的是这样使用线程咱们的回收过程是比较慢的,这个回收速度是咱们计算机性能决定的。

在上面的结果中咱们能够看到咱们总共申请了1996个线程,其中有881个线程是重复的,线程的申请和销毁是耗费不少性能的,接下来咱们看一下线程池。

线程thread有哪些可操做的属性

CurrentContext

获取线程正在其中执行的当前上下文。

CurrentCulture

获取或设置当前线程的区域性。

CurrentPrinciple

获取或设置线程的当前负责人(对基于角色的安全性而言)。

CurrentThread

获取当前正在运行的线程。

CurrentUICulture

获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。

ExecutionContext

获取一个 ExecutionContext 对象,该对象包含有关当前线程的各类上下文的信息。

IsAlive

获取一个值,该值指示当前线程的执行状态。

IsBackground

获取或设置一个值,该值指示某个线程是否为后台线程。

IsThreadPoolThread

获取一个值,该值指示线程是否属于托管线程池。

ManagedThreadId

获取当前托管线程的惟一标识符。

Name

获取或设置线程的名称。

Priority

获取或设置一个值,该值指示线程的调度优先级。

ThreadState

获取一个值,该值包含当前线程的状态。

 

 线程池:ThreadPool

       ThreadPool:线程池中的线程都是后台线程IsBackground属性都是True.不会影响全部的前台线程,也就是说不会影响用户体验。每一个线程都有默认的堆栈大小和优先级,位于多线程池中。 一旦线程池中的线程完成任务,它将返回到等待线程队列中,这时咱们就能够利用这些闲置的线程。经过这种重复使用,应用程序能够避免产生为每一个任务建立新线程的开销。在每个进程中都只会有一个线程池

 

            //public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
            //public static bool SetMinThreads(int workerThreads, int completionPortThreads);
            //workerThreads 工做线程数量  completionPortThreads I/O线程数量
            ThreadPool.SetMaxThreads(12,12);  
            ThreadPool.SetMinThreads(12, 12);

我这里设置工做线程,I/O线程数量来源于我得计算机核心数量,保持每一个核心最大最小线程都启动一个。查看计算机处理器核心数量。

  

上面的设置是说我在线程池中给准备了数量为12 的线程。你能够申请最多12个线程,在使用完以后我会立马进行自动的回收,回收以后的线程继续存放在线程池中等待使用。相比于 Thread线程池ThreadPool对于线程的回收更快,性能更好。

代码看一下性能:

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < 1000; i++)
            {
                Thread thread = new Thread(() =>
                {
                    Console.WriteLine($"*********************************我是多线程Thread方法 *******************************");
                });
                thread.Start();
            }
            Console.WriteLine($"*********************************Thread方法结束  耗费时间 :{stopwatch.ElapsedMilliseconds}  *******************************");

            Console.WriteLine($"*********************************多线程ThreadPool启动  *******************************");

            Stopwatch stopwatch1 = new Stopwatch();
            stopwatch1.Start();
            for (int i = 0; i < 1000; i++)
            {
                    WaitCallback act = (t) =>
                    {
                        Console.WriteLine($"*********************************我是多线程ThreadPool方法 *******************************");
                    };
                    ThreadPool.QueueUserWorkItem(act);
            }
            Console.WriteLine($"*********************************ThreadPool方法结束  耗费时间 :{stopwatch1.ElapsedMilliseconds}  *******************************");

结果:Thread

  

结果:ThreadPool

  

上面的两个方法咱们都只是输出一行字符,可是以1000次的来讲看一下性能相差有多少。因此建议你们都使用线程池。

推荐官方文档:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/?view=netframework-4.7.2

线程池:Task

随着框架的发展咱们有了Task 他也是基于ThreadPool来从新封装的。他的出现方便了咱们对多线程的回调等待更好的操做。

推荐一篇博客:https://www.cnblogs.com/lonelyxmas/p/9509298.html

并行计算的多线程 Parallel

Parallel 多线程,这个相似同步的,他是在Task的基础之上又一次的封装。假如说咱们启动多个线程,她像其余的同样启动了不少的子线程去执行的,而是和当前线程同样并行去执行的。而且当前线程也参与执行。他会卡住线程,等到所有执行完毕后才会继续

这个操做上不如Task 灵活,好比Task 能够等待其中一个线程执行完成后继续主线程,Parallel 是必须等待所有执行完毕。

Parallel里面大体分为三个方法: For,ForEach,Invoke

Invoke:

            Console.WriteLine($"*********************************我是主线程线程 ID:{Thread.CurrentThread.ManagedThreadId} *******************************");
            Action act = () => {

                Console.WriteLine($"个人线程ID是:{Thread.CurrentThread.ManagedThreadId}");
            };
            Parallel.Invoke(act, act, act, act, act);

结果:

  

上面结果中咱们能够看到主线程参与了进来。

ForEach:

        List<int> vs = new List<int>() { 1, 2, 3, 4, 5 };
            Parallel.ForEach<int>(vs, t =>
            {
                Console.WriteLine($"*********************************我是  {t} *******************************");
            });

结果:

  

For

       List<int> vs = new List<int>() { 1, 2, 3, 4, 5 };
            //从零开始 循环多少次
            Parallel.For(0, vs.Count,t=> {
                Console.WriteLine($"*********************************我是  {t} *******************************");
            });

结果:

  

上面的代码中咱们能够看到Parallel 适合在咱们循环的时候去使用这样并行的去执行,咱们能够减小程序的执行时间。

若是当咱们须要执行的集合过大有可能会 并行不少线程时咱们怕会影响咱们计算机的I/O 咱们还能够设置最大的并行数防止程序执行时i/o风暴

       //设置 Parallel 最大并行线程的数量
            ParallelOptions options = new ParallelOptions();
            //最大并行数为10;
            options.MaxDegreeOfParallelism = 10;

Parallel 官方介绍:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.parallel?redirectedfrom=MSDN&view=netframework-4.7.2

获取当前计算机 最大线程数

        int workerThreads;
            int completionPortThreads;
            ThreadPool.GetMaxThreads( out workerThreads, out completionPortThreads);
            Console.WriteLine($"最大工做线程:{workerThreads} 最大I/O线程:{completionPortThreads} ");

            ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
            Console.WriteLine($"最小工做线程:{workerThreads} 最小 I/O线程:{completionPortThreads} ");

结果:

  

有不足之处 但愿你们指出相互学习,

            转载请注明出处 谢谢!

相关文章
相关标签/搜索