C#线程池ThreadPool的理解

在多线程编程中,线程的建立和销毁是很是消耗系统资源的,所以,C#引入了池的概念,相似的还有数据库链接池,这样,维护一个池,池内维护的一些线程,须要的时候从池中取出来,不须要的时候放回去,这样就避免了重复建立和销毁线程。算法

ThreadPool类 MSDN帮助信息: http://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0数据库

将任务添加进线程池:编程

ThreadPool.QueueUserWorkItem(new WaitCallback((方法名));
ThreadPool.QueueUserWorkItem(new WaitCallback((方法名),传入方法的参数);

对线程池的线程数量进行控制多线程

SetMaxThreads(Int32, Int32) //设置能够同时处于活动状态的线程池的请求数目。全部大于此数目的请求将保持排队状态,直到线程池线程变为可用。
 
SetMinThreads(Int32, Int32) //发出新的请求时,在切换到管理线程建立和销毁的算法以前设置线程池按需建立的线程的最小数量。

对线程池线程数量控制的验证函数

public static void Main()
        {
            ThreadPool.SetMinThreads(1, 1);
            ThreadPool.SetMaxThreads(1, 1);

            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => {

                    Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId);

                }),i);
            }

            Console.Read();
        }

最大线程数量和最小线程数量所有设置为1,上述代码的执行结果为:测试

image

能够看到只开启了一个线程。将最大线程改成2spa

public static void Main()
        {
            ThreadPool.SetMinThreads(1, 1);
            ThreadPool.SetMaxThreads(2, 2);

            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => {

                    Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId);

                }),i);
            }

            Console.Read();
        }

此时启动了两个线程线程

image

可是这最多和最少并非说必定要使用这么多线程的,好比,我设置最少10个线程,可是实际上可能只试用了3-4个,可是线程池中确实是最少会维护着10个线程,不必定每次所有都启用的。code

 

public static void Main()
        {
            ThreadPool.SetMinThreads(10, 10);
            ThreadPool.SetMaxThreads(20, 20);

            Console.WriteLine("测试开始!");

            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => {
                    Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId);

                }),i);
            }

            Console.WriteLine("测试结束!");

            Console.Read();
        }

上面的执行结果:blog

image

为何打印测试结束的语句执行的这么靠前呢?这是什么缘由呢?

这是由于在循环中将任务添加到线程池中后,并无等待线程执行完成再继续执行主线程,也就是线程池中的现成是如何启动及结束咱们是不知道的,ThreadPool没有提供简单的方法来获取工做线程是否已经结束,因此须要经过其余方法实现。此时,须要引入ManualResetEvent类,MSDN:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx

 

ManualResetEvent 容许线程经过发信号互相通讯。一般,此通讯涉及一个线程在其余线程进行以前必须完成的任务。

当一个线程开始一个活动(此活动必须完成后,其余线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程能够继续进行的信号。并释放全部等待线程。

一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将当即返回。

能够经过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,若是初始状态处于终止状态,为 true;不然为 false。

方法WaitOne(Timeout.Infinite, true);  阻止当前线程,直到当前 WaitHandle 收到信号为止。

方法Set(); 将事件状态设置为终止状态,容许一个或多个等待线程继续。

这段话究竟是什么意思呢?咱们经过一段代码来理解

public static void Main()
{
    ThreadPool.SetMinThreads(3,3);

    ManualResetEvent mre = new ManualResetEvent(false);

    for (int i = 0; i < 3; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId );

            mre.WaitOne();

            Thread.Sleep(500);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!
线程ID为:"
 + Thread.CurrentThread.ManagedThreadId );

        }), i);
    }

    Console.Read();
}

 

上述代码的执行结果是:

image

从执行结果中能够看到,咱们往线程池中添加了三个任务,线程池启用了三个线程去执行。当任务方法执行到mre.WaitOne();时,线程被ManualResetEvent 阻止,并无继续往下走,也就是此时线程们正在等待一个信号,下面咱们就给线程们发出这个信号。

public static void Main()
{
    ThreadPool.SetMinThreads(3,3);

    ManualResetEvent mre = new ManualResetEvent(false);

    for (int i = 0; i < 3; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId );

            mre.WaitOne();

            Thread.Sleep(500);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId);

        }), i);
    }

    if (Console.ReadLine() == "go")
    {
        mre.Set();
    }


    Console.Read();
}

三个新线程虽然被阻止,可是主线程是能够继续执行的,当主线程收到用户输入的go命令时,给三个线程发送信号,线程们收到信号后继续执行,并打印出执行结束的标识。

image

相信到这里咱们应该可以理解WaitOne和Set的用法了,下面咱们在看看Reset方法,咱们在mre.Set()后,在开启三个新的线程

public static void Main()
{
    ThreadPool.SetMinThreads(3,3);

    ManualResetEvent mre = new ManualResetEvent(false);

    for (int i = 0; i < 3; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId );

            mre.WaitOne();

            Thread.Sleep(600);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId);

        }), i);
    }

    if (Console.ReadLine() == "go")
    {
        mre.Set();
    }

    Thread.Sleep(1000);

    Console.WriteLine("*******************************再开启三个线程***********************************");

    for (int i = 3; i < 6; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId);

            mre.WaitOne();

            Thread.Sleep(600);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId);

        }), i);
    }

    Console.Read();
}

运行代码看看效果

image

新开的线程和以前开的线程的任务方法是如出一辙的啊,为何没有等待信号而直接继续运行了呢?

这是由于咱们在调用ManualResetEvent的Set方法后,在调用其 Reset 方法前会一直保持终止状态,因此,新线程任务方法中的WaitOne是无效的,由于此时ManualResetEvent是终止状态的。下面咱们加上Reset方法看看效果

public static void Main()
{
    ThreadPool.SetMinThreads(3,3);

    ManualResetEvent mre = new ManualResetEvent(false);

    for (int i = 0; i < 3; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId );

            mre.WaitOne();

            Thread.Sleep(600);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId);

        }), i);
    }

    if (Console.ReadLine() == "go")
    {
        mre.Set();
    }

    Thread.Sleep(1000);

    Console.WriteLine("*******************************再开启三个线程***********************************");

    mre.Reset();

    for (int i = 3; i < 6; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
        {
            Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId);

            mre.WaitOne();

            Thread.Sleep(600);

            Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId);

        }), i);
    }

    if (Console.ReadLine() == "go")
    {
        mre.Set();
    }

    Console.Read();
}

执行结果:

image

此时,达到了咱们想要的结果。

最后调用 mre.Close();释放资源便可。

如今明白了ManualResetEvent类的使用,想要解决开始的“测试开始、测试结束”打印顺序问题就是张飞吃豆芽了吧?!

相关文章
相关标签/搜索