原文发布时间为:2010-10-27 —— 来源于本人的百度文章 [由搬家工具导入]

池(Pool)是一个很常见的提高性能的方式。比如线程池连接池等,之所以有这些池是因为线程和数据库连接的创建和关闭是一种比较昂贵的行为。对于这种昂贵的资源我们往往会考虑在一个池容器中放置一些资源,在用的时候去拿,在不够的时候添点,在用完就归还,这样就可以避免不断的创建资源和销毁资源。

如果您做过相关实验的话可能会觉得不以为然,似乎开1000个线程也用不了几百毫秒。我们要这么想,对于一个高并发的环境来说,每一秒假设有100 个请求,每个请求需要使用(开和关)10个线程,也就是一秒需要处理1000个线程的开和关,每个线程独立堆栈1M,可以想象在这一秒中内存分配和回收是多么夸张,这个开销不能说不昂贵。

首先,要理解线程池线程分为两类工作线程和IO线程,可以单独设置最小线程数和最大线程数:

ThreadPool.SetMinThreads(2, 2);
ThreadPool.SetMaxThreads(4, 4);

最大线程数很好理解,就是线程池最多创建这些线程,如果最大4个线程,现在这4个线程都在运行的话,后续进来的线程只能排队等待了。那么为什么有最小线程一说法呢?其实之所以使用线程池是不希望线程在创建后运行结束后理解回收,这样的话以后要用的时候还需要创建,我们可以让线程池至少保留几个线程,即使没有线程在工作也保留。上述语句我们设置线程池一开始就保持2个工作线程和2个IO线程,最大不超过4个线程。

至于线程池的使用相当简单先来看一段代码:

for (int i = 0; i < totalThreads; i++)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        Thread.Sleep(1000);
        int a, b;
        ThreadPool.GetAvailableThreads(out a, out b);
        Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("mm:ss")));
    });
}
Console.WriteLine("Main thread finished");
Console.ReadLine();

代码里面用到了一个事先定义的静态字段:

static readonly int totalThreads = 10;

代码运行结果如下:

每一个线程都休眠一秒然后输出当前线程池可用的工作线程和IO线程以及当前线程的托管ID和时间。我们通过这段代码可以发现线程池的几个特性:

1) 线程池中的线程都是后台线程,如果没有在主线程使用ReadLine的话,程序马上会退出。

2) 线程池一开始就占用了2个线程,一秒后占用了4个线程,工作线程将会由3-6四个线程来处理。

3) 线程池最多使用了4个工作线程和0个IO线程。

那么,我们如何知道线程池中的线程都运行结束了呢,可以想到上文用过的Monitor结构:

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < totalThreads; i++)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        Thread.Sleep(1000);
        int a, b;
        ThreadPool.GetAvailableThreads(out a, out b);
        Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("mm:ss")));
        lock (locker)
        {
            runningThreads--;
            Monitor.Pulse(locker);
        }

    });
}

lock (locker)
{
    while (runningThreads > 0)
        Monitor.Wait(locker);
}

Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();

程序中用到了两个辅助字段:

static object locker = new object();

static int runningThreads = totalThreads;

程序运行结果如下:

我们看到,10个线程使用了3.5秒全部执行完毕。20个线程呢?  

需要6秒。细细分析这2个图我们不难发现,新的线程不是在不够用的时候立即创建而是延迟了0.5秒左右的时间,这是因为线程池会等待一下看是不是有线程在这段时间内可用,如果实在没有的话再创建。其实可以这么理解这6秒,前一秒只有2个线程,后4秒有4个线程执行了16个,最后1秒又只有2个线程了,所以一共是2+4*4+2=20,6秒处理了20个线程。

ThreadPool还有一个很有用的方法可以注册一个信号量,我们发出信号后所有关联的线程才执行,否则就一直等待,还可以指定等待的时间:

首先定义信号量和存储结果的字段:

static ManualResetEvent mre = new ManualResetEvent(false);
static int result = 0;

程序如下:

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < totalThreads; i++)
{
    ThreadPool.RegisterWaitForSingleObject(mre, (state, istimeout) =>
        {
            Thread.Sleep(1000);
            int a, b;
            ThreadPool.GetAvailableThreads(out a, out b);
            Interlocked.Increment(ref result);
            Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("mm:ss")));
            lock (locker)
            {
                runningThreads--;
                Monitor.Pulse(locker);
            }
        }, null, 500, true);
}

Thread.Sleep(1000);
result = 10;
mre.Set();
lock (locker)
{
    while (runningThreads > 0)
        Monitor.Wait(locker);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(result);
Console.ReadLine();

第一个参数就是信号量,第二个参数就是方法主体(接受两个参数分别是传给线程的一个状态变量以及线程执行的时候是否超时),第三个参数是状态变量,第四个参数超时时间我们设置了500毫秒,由于主线程在1秒后发出信号,超时500毫秒,所以这些线程并没等到信号的发出500毫秒之后就运行了。之所以程序的运行结果为30是因为即使500毫秒之后线程超时开始执行但是也要等1秒才累加结果,在这个时候主线程早已把结果更新为10了,所以累加从10开始而不是0开始。最后布尔参数为true表明接受到信号后只线程执行一次。

观察到,所有线程执行完毕花了7秒的时间,除去开始的等待时间0.5秒,相比之前的例子还多了0.5秒的时间。这是为什么呢?请大家帮忙分析分析。还有一个更奇怪的问题是,RegisterWaitForSingleObject消耗的是IO线程而不是工作线程,难道微软觉得 RegisterWaitForSingleObject常见于IO操作的应用还是不希望不浪费工作线程?

线程池基础 ThreadPool基础的更多相关文章

  1. 线程池(ThreadPool)

    线程池概述 由系统维护的容纳线程的容器,由CLR控制的所有AppDomain共享.线程池可用于执行任务.发送工作项.处理异步 I/O.代表其他线程等待以及处理计时器. 线程池与线程 性能:每开启一个新 ...

  2. C#多线程--线程池(ThreadPool)

    先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行, ...

  3. Java线程池(ThreadPool)详解

    线程五个状态(生命周期): 线程运行时间 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间.    如果:T1 + T3 远大于 T2,则可以 ...

  4. python 进程池(multiprocessing.Pool)和线程池(threadpool.ThreadPool)的区别与实例

    一般我们是通过动态创建子进程(或子线程)来实现并发服务器的,但是会存在这样一些缺点: 1.动态创建进程(或线程)比较耗费时间,这将导致较慢的服务器响应.  2.动态创建的子进程通常只用来为一个客户服务 ...

  5. python线程池(threadpool)

    一.安装 pip install threadpool 二.使用介绍 (1)引入threadpool模块 (2)定义线程函数 (3)创建线程 池threadpool.ThreadPool() (4)创 ...

  6. python线程池(threadpool)模块使用笔记

    一.安装与简介 pip install threadpool pool = ThreadPool(poolsize) requests = makeRequests(some_callable, li ...

  7. 线程池之ThreadPool类与辅助线程 - <第二篇>

    一.CLR线程池 管理线程开销最好的方式: 尽量少的创建线程并且能将线程反复利用(线程池初始化时没有线程,有程序请求线程则创建线程): 最好不要销毁而是挂起线程达到避免性能损失(线程池创建的线程完成任 ...

  8. python线程池(threadpool)模块使用笔记 .python 线程池使用推荐

    一.安装与简介 pip install threadpool pool = ThreadPool(poolsize) requests = makeRequests(some_callable, li ...

  9. 转载 线程池之ThreadPool类与辅助线程 - <第二篇>

    http://www.cnblogs.com/kissdodog/archive/2013/03/28/2986026.html 一.CLR线程池 管理线程开销最好的方式: 尽量少的创建线程并且能将线 ...

  10. 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低

    .NET Framework 4.5 开始引入 Task.Run,它可以很方便的帮助我们使用 async / await 语法,同时还使用线程池来帮助我们管理线程.以至于我们编写异步代码可以像编写同步 ...

随机推荐

  1. Atlas 配置高可用

    keepalived安装 #下载keepalived ./configure --prefix=/usr/local Make && make install Atlas主安装keep ...

  2. DOS中断及程序调用

    http://www.cnblogs.com/ynwlgh/archive/2011/12/12/2285017.html

  3. firewall-cmd 防火墙命令详解 及 TCP Wrappers

    firewall-cmd 常用参数及作用 参数 作用 --get-default-zone 查询默认的区域名称 --set-default-zone=<区域名称> 设置默认的区域,使其永久 ...

  4. OpenCV学习笔记(三) 访问像素

    转自:OpenCV如何扫描图像.利用查找表和计时 测试代码:opencv\samples\cpp\tutorial_code\core\how_to_scan_images 测试函数耗时 cv::ge ...

  5. Python虚拟机类机制之填充tp_dict(二)

    填充tp_dict 在Python虚拟机类机制之对象模型(一)这一章中,我们介绍了Python的内置类型type如果要完成到class对象的转变,有一个重要的步骤就是填充tp_dict对象,这是一个极 ...

  6. 基类View

    尽管类视图看上去类的种类繁多,但每个类都是各司其职的,且从类的命名就可以很容易地看出这个类的功能.大致可分为如下三个大的功能块,分别由三个类提供对应的方法: 处理 HTTP 请求.根据 HTTP 请求 ...

  7. “帮你APP”团队冲刺1

    1.整个项目预期的任务量 (任务量 = 所有工作的预期时间)和 目前已经花的时间 (所有记录的 ‘已经花费的时间’),还剩余的时间(所有工作的 ‘剩余时间’) : 所有工作的预期时间:88h 目前已经 ...

  8. 关于前后端日期处理 开发注意事项 jquery.tmpl()函数的使用

    1当后端将日期传到前段的时候 我们通常会需要将日期转为制定格式 除了平常我们使用的前段插件将日期转好 spring @datetimeFormat 注解 这些形式外 我们还可以在实体里通过get方法进 ...

  9. CSS中behavior属性语法简介

    本文和大家重点讨论一下CSS中behavior属性语法的使用,在进行CSS网页布局的时候,我们经遇到刷新要保留表单里内容的时候,习惯的做法使用cookie,但是那样做实在是很麻烦,CSS中的behav ...

  10. Android输入法弹出时覆盖输入框问题

    本文来自网易云社区 作者:孙有军 当一个activity中含有输入框时,我们点击输入框,会弹出输入法界面,整个界面的变化效果与manifest中对应设置的android:windowSoftInput ...