转载自:
http://blog.csdn.net/sanlongcai/archive/2007/08/18/1749949.aspx
Glib-2.12.9中的线程池可以把它看作一个类,进程可以通过这个类创建多个线程池对象。每个线程池对象所创建的线程可以以私有方式使用,也可以以共享方式使用。私有方式是指具体某个线程池对象所创建管理的线程只能是它自己使用,同一进程的其它线程池对象则无权过问。共享方式,顾名思义就是指同一进程的所有线程池对象创建的线程以共享的方式使用,这也正是本文要分析的。关于私有方式的代码分析和下面要用到的异步队列知识可以在我写的另一篇文章中查看。
线程池对象共享线程的内部原理并不复杂,但实现却不是那么简单。特别是共享方式的实现代码要同私有方式和相关的释放管理代码交织在一起更是复杂。当然我们只分析共享方式的相关代码,其它不相关的代码暂不考虑,这样就简单多了。那么先看看它的原理:在同一进程空间的所有线程共享了地址空间,在共享的地址空间中有一个供所有线程池对象共享的全局线程池;当某个具体的线程池对象创建了线程完成任务后,并又不再使用这一刚创建线程时,这一刚创建的线程就交给全局线程池管理;当其它线程池对象想要线程做某一任务时,它就从全局线程池获取线程(没有就自己创建)完成相应的任务,任务完成后又把不再使用的线程放回到全局线程池。
线程池对象共享方式从某一方面看,它的实现利用了线程的本质。传统的创建线程是线程总和某一函数关联在一起,像是鱼离不开水一样。而Glib实现的线程池共享方式就不一样了,线程池对象创建的线程就是线程,它只有同一进程的共享地址空间,而不跟任何函数耦合。因此无论是哪个线程池对象创建的线程,在全局线程池看来都是一样的:就是个线程,即线程和函数完全地解耦。这也是我认为Glib库实现线程池中最为精妙之处。
在gthreadpool.h头文件中有如下的数据结构:
39 /* The real GThreadPool is bigger, so you may only create a thread
40 * pool with the constructor function */
41 struct _GThreadPool
42 {
43 GFunc func;
44 gpointer user_data;
45 gboolean exclusive; // 为TRUE表示不共享方式,FALSE表示共享方式
46 };
接下来的出现的所有代码都是来自gthreadpool.c文件的:
35 typedef struct _GRealThreadPool GRealThreadPool;
36
37 struct _GRealThreadPool
38 {
39 GThreadPool pool;
// 任务异步队列,通过g_thread_pool_push接口把数据放入这队列。一个数据就
// 是一个要被线程池对象处理的任务。
40 GAsyncQueue* queue;
……………………..// 点代表省略的代码
49 };
50
51 /* The following is just an address to mark the wakeup order for a
52 * thread, it could be any address (as long, as it isn't a valid
53 * GThreadPool address) */
54 static gconstpointer const wakeup_thread_marker = (gconstpointer) &g_thread_pool_new;
55 static gint wakeup_thread_serial = 0;
56
57 /* Here all unused threads are waiting */
58 static GAsyncQueue *unused_thread_queue = NULL; // 用于全局线程池
59 static gint unused_threads = 0; // 全局线程池中所持有线程的个数
60 static gint max_unused_threads = 0; // 全局线程池持有线程的上限
线程池对象的创建:
416 GThreadPool*
417 g_thread_pool_new (GFunc func,
418 gpointer user_data,
419 gint max_threads,
420 gboolean exclusive,
421 GError **error)
422 {
423 GRealThreadPool *retval;
…………………
431 retval = g_new (GRealThreadPool, 1);
432
433 retval->pool.func = func;
434 retval->pool.user_data = user_data;
435 retval->pool.exclusive = exclusive;
436 retval->queue = g_async_queue_new ();
…………………
// 管理全局线程池的unused_thread_queue异步队列只被创建一次。这里使用类
// 似pthread_once之类的来控制是否更好???
444 G_LOCK (init);
// 创建用于全局线程池的异步队列unused_thread_queue
445 if (!unused_thread_queue)
446 unused_thread_queue = g_async_queue_new ();
447 G_UNLOCK (init);
…………………
467 return (GThreadPool*) retval;
468 }
结合下面的代码(代码没有按代码行而是按调用先后顺序给出)分析全局线程池管理线程过程:“存储”线程过程和“分配”线程过程。
管理全局线程池的异步队列unused_thread_queue的解释:
“存储”线程到全局线程池是代码128行:…_pop (unused_thread_queue),从全局线程池分配线程是代码356行:…_push_unlocked (unused_thread_queue, pool)。如果有多个线程在全局线程池,那么就有多个线程调用128行的pop接口进入睡眠,也即有多个线程在等待任意一个线程池对象pool的到来并为之服务。当某个具体的线程池对象有任务(数据)要处理,就调用356行的push接口把要处理任务的线程池对象pool交给了在等待pool的任意的一个线程,本质上也就是pool获取了全局线程池中任意一个可用的线程。
全局线程池“存储”线程过程:
1. 假设全局线程池现在还没有线程,主线程通过某个线程池对象pool执行了代码489行的g_thread_pool_push接口。
2. 因为是共享方式,这时它肯定会执行到代码行504行的g_thread_pool_start_thread
3. 343行的g_thread_pool_start_thread函数因全局线程池没有可用线程,主线程会执行到代码366行调用 g_thread_create创建线程。主线程创建了新线程后继续执行,通过调用层次的返回执行到代码行506把任务(数据)放进线程池对象pool。新的被创建的线程从239行的函数g_thread_pool_thread_proxy开始继续执行。这时新线程中的线程池对象pool和主线程执行到代码506行那个pool是同一个。接下来看新线程是如何“存储”到全局线程池。
4. 新线程执行到254行获取任务,如果pool还没有任务(主线程还没有把任务放入pool),新线程最会等待1/2秒,这时间足够让主线程把任务放入pool。因此新线程执行到254行获取就能得到一个待处理的任务,并在265行代码处理任务。
5. 新线程处理完任务后就进入了下一次循环。当它又执行代码254行获取任务时,这时因pool中没有任务了,函数 g_thread_pool_wait_for_new_task就返回一个空的任务。然后就会执行到324行调用函数 g_thread_pool_wait_for_new_pool。
6. 代码88行的函数g_thread_pool_wait_for_new_pool会执行到代码128行调用函数g_async_queue_pop接口。到此被创建的新线程就“存储”到了全局线程池。从代码324行可看出,线程所使用的pool已不再局限于主线程刚创建新线程时所给的pool了,也就是达到了所有的pool都可以共享此新线程。
全局线程池“分配”线程过程:
1. 同“存储”线程过程的前两步一样执行到343行的g_thread_pool_start_thread函数。
2. 因为全局线程池已有可用的空闲线程,所以函数g_thread_pool_start_thread会执行到代码行356调用函数 g_async_queue_push_unlocked把当前的线程池对象pool给异步队列unused_thread_queue,也就是通过入队的pool唤醒一个等待线程(可用的空闲线程)。
3. 主线程唤醒了一个等待线程后继续执行,通过调用层次的返回执行到代码行506把任务(数据)放进当前线程池对象pool。由“存储”线程过程的第6步可知,线程的等待是在代码128行调用函数g_async_queue_pop,因此被唤醒的线程由这里开始执行。而…_pop接口返回的pool就是主线程调用…_push放入的有任务要处理的pool。
4. 被唤醒的线程执行到代码324行,然后又是从代码250行的循环开始执行。
5. 接下来的执行过程跟“存储”线程过程后三个步骤是一样的。
489 g_thread_pool_push (GThreadPool *pool,
490 gpointer data,
491 GError **error)
492 {
493 GRealThreadPool *real;
494
495 real = (GRealThreadPool*) pool;
………………………
500 g_async_queue_lock (real->queue);
501
// “>= 0”表示当前线程池对象没拥有空闲的线程。为什么“>= 0”就是这种情况
// ,这是异步队列的知识,可以参考上面提供的链接。
// 其实如果是共享方式使用当前线程池对象的话,当前线程池对象就不应拥有空闲线程,
// 因为空闲线程都交给全局线程池了。
502 if (g_async_queue_length_unlocked (real->queue) >= 0)
503 /* No thread is waiting in the queue */
504 g_thread_pool_start_thread (real, error);
505
506 g_thread_pool_queue_push_unlocked (real, data);
507 g_async_queue_unlock (real->queue);
508 }
343 g_thread_pool_start_thread (GRealThreadPool *pool,
344 GError **error)
345 {
346 gboolean success = FALSE;
347
// 判断当前线程池拥有的线程是否已经达到它的上限,如果是就返回。
348 if (pool->num_threads >= pool->max_threads && pool->max_threads != -1)
349 /* Enough threads are already running */
350 return;
351
352 g_async_queue_lock (unused_thread_queue);
353
// 判断全局线程池是否有可用的空闲线程,如果有
// 就调用g_async_queue_push_unlocked (unused_thread_queue, pool)让
// 当前线程池对象获取一个空闲的线程
354 if (g_async_queue_length_unlocked (unused_thread_queue) < 0)
355 {
356 g_async_queue_push_unlocked (unused_thread_queue, pool);
357 success = TRUE;
358 }
359
360 g_async_queue_unlock (unused_thread_queue);
361
// 如果全局线程池没有可用线程,success还是为FALSE,
// 则调用g_thread_create新建一个线程
362 if (!success)
363 {
364 GError *local_error = NULL;
365 /* No thread was found, we have to start a new one */
366 g_thread_create (g_thread_pool_thread_proxy, pool, FALSE, &local_error);
………………………
373 }
374
375 /* See comment in g_thread_pool_thread_proxy as to why this is done
376 * here and not there
377 */
378 pool->num_threads++;
379 }
239 g_thread_pool_thread_proxy (gpointer data)
240 {
241 GRealThreadPool *pool;
242
243 pool = data;
………………………
248 g_async_queue_lock (pool->queue);
249
250 while (TRUE)
251 {
252 gpointer task;
253
// 如果task不为空,当前线程就用线程池对象的任务处理函数pool->pool.func处理任
// 务,处理完后继续循环。
// 如果task为空并且是共享方式,就调用g_thread_pool_wait_for_new_pool把当前
// 线程交给了全局线程池。
254 task = g_thread_pool_wait_for_new_task (pool);
255 if (task)
256 {
257 if (pool->running || !pool->immediate)
258 {
259 /* A task was received and the thread pool is active, so
260 * execute the function.
261 */
262 g_async_queue_unlock (pool->queue);
263 DEBUG_MSG (("thread %p in pool %p calling func.",
264 g_thread_self (), pool));
// 线程根据线程池对象pool处理任务
265 pool->pool.func (task, pool->pool.user_data);
266 g_async_queue_lock (pool->queue);
267 }
268 }
269 else
270 {
271 /* No task was received, so this thread goes to the global
272 * pool.
273 */
…………………….
324 if ((pool = g_thread_pool_wait_for_new_pool ()) == NULL)
325 break;
…………………….
336 }
337 }
338
339 return NULL;
340 }
180 static gpointer
181 g_thread_pool_wait_for_new_task (GRealThreadPool *pool)
182 {
183 gpointer task = NULL;
184
185 if (pool->running || (!pool->immediate &&
186 g_async_queue_length_unlocked (pool->queue) > 0))
187 {
188 /* This thread pool is still active. */
189 if (pool->num_threads > pool->max_threads && pool->max_threads != -1)
190 {
191 /* This is a superfluous thread, so it goes to the global pool. */
192 DEBUG_MSG (("superfluous thread %p in pool %p.",
193 g_thread_self (), pool));
194 }
195 else if (pool->pool.exclusive) // 不共享方式
196 {
……………………..
204 }
205 else // 共享方式,等待1/2秒后返回空的task
206 {
207 /* A thread will wait for new tasks for at most 1/2
208 * second before going to the global pool.
209 */
210 GTimeVal end_time;
211
212 g_get_current_time (&end_time);
213 g_time_val_add (&end_time, G_USEC_PER_SEC / 2); /* 1/2 second */
214
215 DEBUG_MSG (("thread %p in pool %p waits for up to a 1/2 second for task "
216 "(%d running, %d unprocessed).",
217 g_thread_self (), pool, pool->num_threads,
218 g_async_queue_length_unlocked (pool->queue)));
219
220 task = g_async_queue_timed_pop_unlocked (pool->queue, &end_time);
221 }
222 }
223 else
224 {
225 /* This thread pool is inactive, it will no longer process tasks. */
……………….
232 }
233
234 return task;
235 }
// 从“存储”当前线程到全局线程池看,函数的功能是把当前线程交给全局线程池。
// 从线程池对象获取线程看,函数的功能是从全局线程池“分配”线程,即被返回的
// pool获得了一个线程。
87 static GRealThreadPool*
88 g_thread_pool_wait_for_new_pool (void)
89 {
90 GRealThreadPool *pool;
………………….
// 全局线程池中拥有的线程个数加一,回为当前线程要进入到全局线程池。
101 g_atomic_int_inc (&unused_threads);
102
103 do
104 {
105 if (g_atomic_int_get (&unused_threads) >= local_max_unused_threads)
106 {
107 /* If this is a superfluous thread, stop it. */
108 pool = NULL;
109 }
110 else if (local_max_idle_time > 0)
111 {
112 /* If a maximal idle time is given, wait for the given time. */
113 GTimeVal end_time;
……………………
// 利用异步队列的pop接口把当前线程交给全局线程池,使它自己进入等待状态
121 pool = g_async_queue_timed_pop (unused_thread_queue, &end_time);
122 }
123 else
124 {
125 /* If no maximal idle time is given, wait indefinitely. */
………………………
// 利用异步队列的pop接口把当前线程交给全局线程池,使它自己进入等待状态
128 pool = g_async_queue_pop (unused_thread_queue);
129 }
………………………
172 }
173 while (pool == wakeup_thread_marker);
174
// 全局线程池中拥有的线程个数减一,回为当前线程离开了全局线程池,
// pool获取了当前线程。
175 g_atomic_int_add (&unused_threads, -1);
176
177 return pool;
178 }