关于线程池实现思考——分离任务队列与线程池

    设计线程池时,本质上所使用的逻辑模型仍然是我们熟悉的“生产者/消费者”模型。

    外部线程负责产生需要执行的任务,线程池线程负责执行这些任务。任务放在一个共享的数据结构中,通常是一个线程安全的队列。

                                 生产            消费
    外部线程(生产者)--->任务<---线程池线程(消费者)
    通常,任务对象会提供一个run()方法,用于外部调用者执行任务。
    最近看JDK1.5的并发功能,发现我以前在Windows编程中实现线程池时,思路存在问题,造成了程序复杂度增加。
    下面整理一下线程池的实现方法,比较一下我以前的实现思路和JDK的实现思路。


*我以前实现线程池的思路
********************************************************************************

一、基本思路

    线程池内部维护一个线程数组。
    外部线程和线程池线程共享一个任务队列。
    如果是Windows编程,队列中可以设置一个信标(Semephore),表示队列中元素的个数,初始值为0,最大值为队列的上限。假设这个信标命名为QueueSize。
    线程池在执行任务时,会在内部调用task.run()方法。

    外部线程的执行流程:
    1、产生任务;
    2、则调用队列queue.add(task)将任务添加到队列中;
    3、如果需要,重复以上过程。
    线程池线程的执行流程:
    1、检查任务队列;
    2、调用队列queue.remove()将任务重队列中取出;
    3、执行上一步得到的任务;
    4、反复以上过程。
    队列的实现:
    1、队列的add(task)方法实现时,递增QueueSize;
        function void queue::add(task) {
            //如果达到队列上限,则线程被阻塞
            ReleaseSemaphore(QueueSize);
            //同步修改队列
            EnterCriticalSection(cs);
            _queue.add(task);
            LeaveCriticalSection(cs);
            return;
        }
    2、队列的remove()方法实现时,递减QueueSize;
        function &task queue::remove() {
            //如果queueSize==0,则线程被阻塞
            WaitForSingleObject(QueueSize);
            //同步修改队列
            EnterCriticalSection(cs);
            task &ret = _queue.remove();
            LeaveCriticalSection(cs);
            return ret;
        }
二、应用时的伪代码。
    线程池的创建,通常在主线程中进行:
    queue = new TaskQueue(queueSize);
    pool = new ThreadPool(queue, poolSize);

    某个外部线程的执行片段:
    //...
    task = new Task();
    //...
    queue.add(task);
三、小结
    用上述思路实现的线程池,实现了线程池的功能的同时,也提供了任务队列缓存功能,看起来似乎不错。
    但仔细思考我们就会发现,线程池的内部实现调用了任务队列的操作接口,使得线程池和队列偶合了起来。

 

※JDK实现线程池的思路
********************************************************************************
一、基本思路
    线程池内部维护一个线程数组。线程池对外提供一个execute(task)接口,内部回调task.run()方法。
    外部线程的执行流程:
    1、产生任务;
    2、把直接交给线程池执行,当然也可以交给一个任务管理器来执行;
        pool.execute(task);
    3、如果需要,重复上述步骤。
    线程池的执行过程:
    1、如果有任务且池中有空余线程,则执行任务;
    2、否则,池中线程阻塞等待;
    3、重复以上过程。

二、应用时的伪代码:

    线程池的创建:
    pool = new ThreadPool(poolSize);

    某个外部线程的执行片段:
    //...
    task = new Task();
    //...
    pool.execute(task);
    queue = new TaskQueue(queueSize);

三、小结
    可以发现,JDK的线程池的实现时,做到了完全与队列剥离。不但降低了复杂度,而且容易通过编写外部的任务管理器来调度线程。
    JDK线程池的设计,与我以前自己设计的线程池相比,更符合SRP(单一职责原则)和低耦合原则,代码也更容易维护。值得好好学习!

你可能感兴趣的:(C/C++编程,Java编程,Windows操作系统,任务,jdk,function,windows,数据结构,编程)