代码改变世界

C# 线程手册 第五章 多线程应用程序 .NET 中的扩展性

2012-04-19 22:27  DanielWise  阅读(2703)  评论(6编辑  收藏  举报

  如果你有一个多处理器系统,那么你将有机会体验到线程真正的价值。Windows 操作系统负责向处理器分配线程,正如你在本书中看到的那样,启动一个进程会自动运行一个线程。.NET Framework 不提供细粒度的处理器分配控制,而是允许操作系统控制调度,这是由于操作系统比CLR 更加了解处理器的负载。CLR 负责提供一些诸如整个进程运行在哪个处理器上的控制。然而,一个进程中的所有线程都会运行在一个处理器上,控制进程中的线程运行在哪个处理器上的内容不会在本书中介绍。

  如果你只有一个主线程,那么在这个线程中的每个任务都会运行在同一个处理器上。然而,如果系统中又新建了一个线程,那么操作系统负责调度这个新生成的线程运行在哪个处理器上。决定在哪个处理器上运行线程也是要消耗一些处理器资源的,对小任务来说,这种决定的消耗通常来说是不值得的,因为这个任务的运行时间可能和操作系统将任务调度到处理器上的时间差不多。然而,这种调度消耗的时间在Windows 各版本的持续改进中已经越来越短了,对那些不是特别琐碎的任务来说,使用线程会让你的任务获得性能方面的提升。在对称多处理器系统(SMP)中使用线程的优势非常明显,因为处理器可以高效的处理应用程序的负载均衡。

  在下一部分,我们将要描述如何创建一个管理线程的线程池管理器,并可以保证线程池中的最小和最大线程数量和重用空闲线程。

一个线程池管理器

  纵观本书,你已经看到创建线程的几种方式,同时在这一章我们已经描述了ThreadPool 类为生命周期短的线程使用操作系统自己的线程池。我们可以创建一个为任何应用程序的请求提供一个特定数量线程的类。这将使线程更容易地被你自己的代码所管理,由于你可以使用一个之前已经实例化的线程对象所以线程执行起来会更快。这个类将把到目前为止我们学到的所有知识串联起来,你将可以在自己的多线程应用程序中使用它。我们将一步步的解释这个类,最后会提供一个应用程序来测试这个类是否按照我们期望的那样执行。

  好吧,我们现在就开始实现我们的线程池类:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

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

namespace GenThreadPool
{
    public interface IThreadPool
    {
        void AddJob(Thread jobToRun);
        Stats GetStats();
    }
}
public void AddJob(Thread jobToRun)
{
    if (jobToRun != null)
    {
        lock (this)
        {
            PendingJobs.Add(jobToRun);
            //int index = FindFirstIdleThread();
            int index = 0;
            if (mDebug)
            {
                Console.WriteLine("First idle thread is " + index);
            }
            if (index == -1)
            {
                if ((mMaxThreads == -1) || (mAvailableThreads.Count < mMaxThreads))
                {
                    if (mDebug)
                    {
                        Console.WriteLine("Creating a new thread");
                    }
                    Thread t = new Thread(new ThreadStart(new GenPool(this, this).Run));
                    ThreadElement e = new ThreadElement(t);
                    e.Idle = false;
                    e.GetMyThread().Start();

                    try
                    {
                        mAvailableThreads.Add(e);
                    }
                    catch (OutOfMemoryException)
                    {
                        Console.WriteLine("Out of Memory");
                        mAvailableThreads.Add(e);
                        Console.WriteLine("Added Job again");
                    }
                    return;
                }
                if (mDebug)
                {
                    Console.WriteLine("No Threads Available..." + this.GetStats().ToString());
                }
                else
                {
                    try
                    {
                        if (mDebug)
                        {
                            Console.WriteLine("Using an existing thread...");
                        }
                        ((ThreadElement)mAvailableThreads[index]).Idle = false;
                        lock (((ThreadElement)mAvailableThreads[index]).GetMyThread())
                        {
                            Monitor.Pulse(((ThreadElement)mAvailableThreads[index]).GetMyThread());
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error while resuing thread " + ex.Message);
                        if (mDebug)
                        {
                            Console.WriteLine("Value of index is " + index);
                            Console.WriteLine("Size of available threads is " + mAvailableThreads.Count);
                            Console.WriteLine("Availablee Threads is " + mAvailableThreads.IsSynchronized);
                        }
                    }
                }//end of else
            }//lock
        }//end of method
    }
}

上面的方法向线程池中添加一个任务。如果参数传递的任务不存在,那么就会退出这个方法。否则,它会在GenThreadPoolImpl实例上提供一个锁来保证没有其他线程尅添加或者删除一个任务。当任务被添加到列表中以后,列表中将存储有所有等待执行和已经完成的任务。FindFirstIdleThread() 方法返回可用线程列表中当前空闲的线程。如果返回值为-1则表示当前无可用线程,那么线程池就会尝试新建一个线程。

public void RemoveThread()
{
    for (int i = 0; i < mAvailableThreads.Count; i++)
    {
        if (((ThreadElement)mAvailableThreads[i]).GetMyThread().Equals(Thread.CurrentThread))
        {
            mAvailableThreads.RemoveAt(i);
            return;
        }
    }
}

  上述方法会将当前线程从线程池中移除。当然,这个方法也可以用来将任务已经完成的线程和空闲时间超过MaxIdleTime所限定的时间线程移除。现在我们定义这个程序集的其他类:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

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

namespace GenThreadPool
{
    public class GenPool
    {
        private object mLock;
        private GenThreadPoolImpl mGn;

        public GenPool(object lockObj, GenThreadPoolImpl gn)
        {
            mLock = lockObj;
            mGn = gn;
        }

        public void Run()
        {
            Thread job = null;
            try
            {
                while (true)
                {
                    while (true)
                    {
                        lock (mLock)
                        {
                            if (mGn.PendingJobs.Count == 0)
                            {
                                int index = mGn.FindFirstIdleThreadCount();
                                if (index == -1)
                                {
                                    return;
                                }
                                ((ThreadElement)mGn.AvailableThreads[index]).Idle = true;
                                break;
                            }
                            job = (Thread)mGn.PendingJobs[0];
                            mGn.PendingJobs.RemoveAt(0);
                        }//end of lock
                        //run the job
                        job.Start();
                    }

                    try
                    {
                        lock (this)
                        {
                            if (mGn.MaxIdleTime == -1)
                            {
                                Monitor.Wait(this);
                            }
                            else
                            {
                                Monitor.Wait(this, mGn.MaxIdleTime);
                            }
                        }
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }

                    try
                    {
                        lock (mLock)
                        {
                            if (mGn.PendingJobs.Count == 0)
                            {
                                if (mGn.MinThreads != -1 && mGn.AvailableThreads.Count > mGn.MinThreads)
                                {
                                    mGn.RemoveThread();
                                    return;
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

  GenPool 类执行所有的阻塞线程,一旦线程的任务完成,那么在超过MaxIdleTime 时间后就会将符合条件的线程移除。Run() 方法开启一个循环来尝试从线程池中找到与当前线程匹配的线程,并执行它。通过代码你可以发现它会锁住一个对象,如果没有阻塞任务的话,那么它仅仅需要找到线程池中与当前线程匹配的线程,返回-1表示没有找到。如果有一个阻塞任务,那么它会返回第一个阻塞任务并将其从队列中移除。

 

总结

  在这一章,我们已经了解了线程池可以如何应用在运行时间比较短的线程上面。线程池技术允许回收线程。一个线程关联到一个任务上,当那个任务完成以后,线程会返回到线程池中并等待下一次任务。我们也介绍了在.NET 中使用线程池的几个方面。我们开始定义了什么是一个线程池以及我们为什么要在应用程序中使用线程池。我们也介绍了CLR 在创建线程池过程中扮演的角色和在使用线程池中可能遇到的问题。

  我们稍后介绍了一些扩展性问题,ThreadPool 类不适用于长时间运行的任务。我们讨论了如何创建一个ThreadPool 管理器以及SMP系统是如何通过使用线程提高应用程序的性能的。

 

下一篇我们介绍 C# 线程手册 第六章 线程调试与跟踪…