多线程编程学习笔记——线程池

Posted DotNet菜园

tags:

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

接上文 多线程编程学习笔记——线程池(一)

 

三、线程池与并行度

此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别。

1. 代码如下

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; 

namespace ThreadPoolDemo
{  

    class Program
    {   

        static void Main(string[] args)
        {

            Console.WriteLine("开始测试线程池与自创线程。。。");
            const int total = 500;
            long millisecondes = 0;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ThreadRun(total);
            sw.Stop();
            millisecondes = sw.ElapsedMilliseconds;
            decimal mom = (decimal)(Environment.WorkingSet / (1024 * 1024.0));            sw.Reset();

            sw.Start();
            ThreadPoolRun(total);

            sw.Stop();
            Console.WriteLine("自创线程总耗时 {0},占用内存:{1} MB", millisecondes,mom);
            Console.WriteLine("线程池总耗时 {0} ,占用内存:{1} MB", sw.ElapsedMilliseconds, Environment.WorkingSet / (1024 * 1024.0));
            Console.Read();

        }

        private static void  ThreadRun(int total)
        {
            using (var countdown = new CountdownEvent(total))
            {
                Console.WriteLine("开始--自创线程。。。");
                for (int i = 0; i < total; i++)
                {
                    var t = new Thread(() =>
                    {
                        Console.WriteLine("自创线程ID :{0}", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();//向 CountdownEvent 注册信号,同时减小 CurrentCount 的值。

                    });
                    t.Start();
                }
                countdown.Wait();  // 阻塞当前线程,直到 CountdownEvent 的信号数量变为 0
                Console.WriteLine("-----------------");
            }
        }

        private static void ThreadPoolRun(int total)
        {
            using (var countdown = new CountdownEvent(total))

            {
                Console.WriteLine("开始--线程池。。。");
                for (int i = 0; i < total; i++)
                {
                    ThreadPool.QueueUserWorkItem(_ =>
                    {
                        Console.WriteLine("线程池工作线程ID :{0}", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();//向 CountdownEvent 注册信号,同时减小 CurrentCount 的值。
                    });                 

                }
                countdown.Wait();  // 阻塞当前线程,直到 CountdownEvent 的信号数量变为 0
                Console.WriteLine("-----------------");

            }
        }
    }
}

 

2.程序运行结果如下图。

 

    1) 这个示例中我们自己创建了500个线程,每个线程一个操作,每个线程都阻塞100毫秒。总计耗时  11秒,消耗资源如下图。

 

    2)我们使用线程池执行相同的500个操作。总计耗时  9秒,消耗资源如下图。

 

从1)与2)的比较上可以看出来,自创线程消耗的CPU资源比线程池要多。

 

四、 从线程池中取消操作

如果我们要从线程池中取消某个线程的操作,应该如何实现呢?本示例使用CancellationTokenSource和CancellationToken两个类来实现在取消线程池中的操作。这两个是是在net 4.0引入的。

 1.示例代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; 

namespace ThreadPoolDemo
{

    class Program
    {    

        static void Main(string[] args)
        {
            Console.WriteLine("开始测试线程池中取消正在运行的线程。。。");
            using (var cts = new CancellationTokenSource())
            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOper(token));
                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消线程操作
            }

            using (var cts = new CancellationTokenSource())
            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOperation(token));
                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消线程操作

            }
            using (var cts = new CancellationTokenSource())

            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOper3(token));

                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消线程操作
            }

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("。。。。。。。。。。取消正在运行的线程结束。。。。。。");
            Console.Read();
        }

        private static void  AsyncOperation(CancellationToken token)
        {

            try
            {
                Console.WriteLine("开始--线程池中的第二个工作线程。。。");
                for (int i = 0; i < 5; i++)

                {
                    token.ThrowIfCancellationRequested();//获取取消请求,抛出OperationCanceledException异常,
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                }

                Console.WriteLine("-------线程池中的第二个工作线程 工作完成----------");
            }
            catch (OperationCanceledException ex)
            {
                Console.WriteLine("使用抛出异常方法取消第二个工作线程  ID:{0},{1}", Thread.CurrentThread.ManagedThreadId,ex.Message);
            }
        }
        private static void AsyncOper(CancellationToken token)
        {

            Console.WriteLine("开始--线程池中的第一个工作线程。。。");
            for (int i = 0; i < 5; i++)
            {
                if (token.IsCancellationRequested)//判断是否已经取消操作
                {
                    Console.WriteLine("使用轮询方法取消工作线程  ID:{0}", Thread.CurrentThread.ManagedThreadId);
                    return;
                }
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
            Console.WriteLine("-------线程池中的第一个工作线程 工作完成----------");
        }
        private static void AsyncOper3(CancellationToken token)
        {
            Console.WriteLine("开始--线程池中的第三个工作线程。。。");
            bool cancel = false;
            token.Register(()=>cancel = true);
            for (int i = 0; i < 5; i++)
            {
                if (cancel)//判断是否已经取消操作
                {
                    Console.WriteLine("通过注册回调函数取消第三个工作线程  ID:{0}", Thread.CurrentThread.ManagedThreadId);
                    return;
                }
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }   

            Console.WriteLine("-------线程池中的第三个工作线程 工作完成----------");
        }
    }
}

2.运行结果如下图。

 

本示例一共实现了三种取消线程池中操作的方式。

  1. 轮询检查CancellationToken.IsCancellationRequested属性,如果为true,则说明操作被取消。
  2. 抛出一个OperationCancellationException异常。这允许操作之外的代码来取消操作。
  3. 注册一个回调函数,当操作取消时,线程池将调用回调函数,这样做的好处是将取消操作逻辑传递到另一个异步操作中。

以上是关于多线程编程学习笔记——线程池的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程学习笔记

《C++多线程编程》学习笔记

多线程编程学习笔记——线程同步

多线程编程

多线程学习 线程池笔记

Java多线程高并发学习笔记——深入理解线程池