異步隊列的數據結構如下:
struct _GAsyncQueue
{
GMutex *mutex; //互斥變量
GCond *cond; //等待條件
GQueue *queue; //數據隊列
guint waiting_threads; //等待的讀線程個數
gint32 ref_count;
};
g_async_queue_push_unlocked (GAsyncQueue* queue, gpointer data)
{
………//這些點代表一些省略的代碼
//把數據放入隊列
g_queue_push_head (queue->queue, data);
//現在隊列已經有數據了,判斷是否有讀線程在等待數據,
//如果有就發送信號喚醒讀線程
if (queue->waiting_threads > 0)
g_cond_signal (queue->cond);
}
g_async_queue_push (GAsyncQueue* queue, gpointer data)
{
……..
g_mutex_lock (queue->mutex); //在訪問臨界區前先獲得互斥變量
g_async_queue_push_unlocked (queue, data); //執行寫數據操作
g_mutex_unlock (queue->mutex); //釋放互斥變量,以使其它線程可以進入臨界區
}
從以上的接口可看出,”…._ unlocked” 這樣的接口就是異步隊列這個對象已獲得互斥變量的接口,glib中線程處理相關接口都有類似的命名規則,在接下來的代碼分析中,如沒有特別的需要就只看”…._ unlocked” 這樣的接口。
// 讀線程從異步隊列中獲取數據的接口
// try參數和時間參數在多線程同步/內核多進程的實現中是很常見的東西了,在這裏就不再作特殊的解釋了。
g_async_queue_pop_intern_unlocked (GAsyncQueue *queue,
gboolean try,
GTimeVal *end_time)
{
gpointer retval;
//判斷是否有數據在隊列中,如果沒有就要執行if語句相應的睡眠等待,直到被寫進程喚醒
if (!g_queue_peek_tail_link (queue->queue))
{
if (try)//如果try爲真,則永遠不睡眠
return NULL;
// 接下來是要讓線程進行睡眠等待了,在等待之前先確保等待條件已創建
if (!queue->cond)
queue->cond = g_cond_new ();
if (!end_time) // 等待無時間限制
{
queue->waiting_threads++; //
等待線程數加一
// 這裏爲什麼用循環?因爲這是多線程的環境,有可能有多個讀線程在等待
// 當前線程被喚醒時,有可能數據隊列中的數據又被別的線程讀走了,所以
// 當前線程就得繼續睡眠等待
// 注意:睡眠等待時會暫時放棄互斥鎖,被喚醒時會重新獲取互斥鎖
while (!g_queue_peek_tail_link (queue->queue))
g_cond_wait (queue->cond, queue->mutex);
queue->waiting_threads--; //
等待線程數減一
}
else
{
queue->waiting_threads++;
while (!g_queue_peek_tail_link (queue->queue))
if (!g_cond_timed_wait (queue->cond, queue->mutex, end_time))
break;
queue->waiting_threads--;
if (!g_queue_peek_tail_link (queue->queue))
return NULL;
}
}
retval = g_queue_pop_tail (queue->queue);
g_assert (retval);
return retval;
}
/* 返回數據隊列的長度,也即數據隊列中的數據個數.
* 如果是負值表明是等待數據的線程個數,正數表示數據隊列的數據個數
* g_async_queue_length == 0 表示是有 'n' 個數據和' n' 個等待線程在數據隊列
* 這種特殊情況可能是在對數據隊列加鎖或調度時發生
*/
g_async_queue_length_unlocked (GAsyncQueue* queue)
{
g_return_val_if_fail (queue, 0);
g_return_val_if_fail (g_atomic_int_get (&queue->ref_count) > 0, 0);
return queue->queue->length - queue->waiting_threads;
} |
typedef struct _GThreadPool GThreadPool;
struct _GThreadPool
{
// 具體處理數據的函數
// 它的第一個參數爲g_thread_pool_push進去的數據,也即要執行的任務
GFunc func;
gpointer user_data; // func的第二個參數
// 通過這個成員控制線程池對象創建的線程是否在全局線程池中共享,
// TRUE爲不共享,FALSE爲共享
gboolean exclusive;
}; |
typedef struct _GRealThreadPool GRealThreadPool;
struct _GRealThreadPool
{
GThreadPool pool; // 頭文件已定義
GAsyncQueue* queue; // 異步數據隊列
GCond* cond;
gint max_threads; // 線程池對象持有的線程數上限
gint num_threads;// 池程池對象當前持有的線程數
gboolean running;
gboolean immediate;
gboolean waiting;
GCompareDataFunc sort_func;
gpointer sort_user_data;
}; |
// max_threads爲 -1 時表示線程池中的線程數無限制並且線程由動態生成
// max_threads爲正整數時,線程池就會預先創建max_threads個線程
g_thread_pool_new (GFunc func,
gpointer user_data,
gint max_threads,
gboolean exclusive,
GError **error)
{
GRealThreadPool *retval;
……………. //這些點代表一些省略的代碼
retval = g_new (GRealThreadPool, 1);
retval->pool.func = func;
retval->pool.user_data = user_data;
retval->pool.exclusive = exclusive;
retval->queue = g_async_queue_new (); // 創建異步隊列
retval->cond = NULL;
retval->max_threads = max_threads;
retval->num_threads = 0;
retval->running = TRUE;
…………….
if (retval->pool.exclusive)
{
g_async_queue_lock (retval->queue);
while (retval->num_threads < retval->max_threads)
{
GError *local_error = NULL;
g_thread_pool_start_thread (retval, &local_error);//起動新的線程
…………….
}
g_async_queue_unlock (retval->queue);
}
return (GThreadPool*) retval;
}
g_thread_pool_start_thread (GRealThreadPool *pool,
GError **error)
{
gboolean success = FALSE;
if (pool->num_threads >= pool->max_threads && pool->max_threads != -1)
/* Enough threads are already running */
return;
…………….
if (!success)
{
GError *local_error = NULL;
/* No thread was found, we have to start a new one */
// 真正創建一個新的線程
g_thread_create (g_thread_pool_thread_proxy, pool, FALSE, &local_error);
……………….
}
pool->num_threads++;
}
g_thread_pool_thread_proxy (gpointer data)
{
GRealThreadPool *pool;
pool = data;
……………..
g_async_queue_lock (pool->queue);
while (TRUE)
{
gpointer task;
// 線程等待任務,也即等待數據,線程在等待就是處在線程池中的空閒線程
task = g_thread_pool_wait_for_new_task (pool);
// 如果線程被喚醒收到並數據就用此線程執行任務,否則繼續循環等待
// 注意:當任務做完時,繼續循環又會調用上面的g_thread_pool_wait_for_new_task
// 而進入等待狀態,
if (task)
{
if (pool->running || !pool->immediate)
{
/* A task was received and the thread pool is active, so
* execute the function.
*/
g_async_queue_unlock (pool->queue);
pool->pool.func (task, pool->pool.user_data);
g_async_queue_lock (pool->queue);
}
}
else
{
………………
}
}
return NULL;
}
g_thread_pool_wait_for_new_task (GRealThreadPool *pool)
{
gpointer task = NULL;
if (pool->running || (!pool->immediate &&
g_async_queue_length_unlocked (pool->queue) > 0))
{
/* This thread pool is still active. */
if (pool->num_threads > pool->max_threads && pool->max_threads != -1)
{
…………..
}
else if (pool->pool.exclusive)
{
/* Exclusive threads stay attached to the pool. */
// 調用異步隊列的pop接口進入等待狀態,到此一個線程的創建過程就完成了
task = g_async_queue_pop_unlocked (pool->queue);
}
else
{
………….
}
}
else
{
…………
}
return task;
} |
現在可以結合流程圖分析線程池中創建一個線程的一個情景:從函數g_thread_pool_new的while循環調用了 g_thread_pool_start_thread函數,在函數中直接調用g_thread_create創建線程,被創建的線程調用函數 g_thread_pool_wait_for_new_task循環等待任務的到來,函數 g_thread_pool_wait_for_new_task調用g_async_queue_pop_unlocked (pool->queue)真正進入等待。如此可知,最終新創建的線程是調用異步隊列的pop接口進入等待狀態的,這樣一個線程的創建就大功告成 了。而函數g_thread_pool_new的while循環結束時就創建了max_threads個等待線程,也即這個新建的線程池對象有了 max_threads個線程以備使用。
創建線程池、線程池中的線程是爲了使用它,在線程池中取線程,叫線程幹活的過程就很簡單多了,這個調用過程:g_thread_pool_push--à g_thread_pool_queue_push_unlocked--à g_async_queue_push_unlocked。可見最終調用的是異步數據隊列的push接口,把要處理的數據插入隊列後它就會喚醒等待異步隊列數據的等待線程。
g_thread_pool_push (GThreadPool *pool,
gpointer data,
GError **error)
{
……………
//
if (g_async_queue_length_unlocked (real->queue) >= 0)
/* No thread is waiting in the queue */
g_thread_pool_start_thread (real, error);
g_thread_pool_queue_push_unlocked (real, data);
g_async_queue_unlock (real->queue);
}
g_thread_pool_queue_push_unlocked (GRealThreadPool *pool,
gpointer data)
{
………….
g_async_queue_push_unlocked (pool->queue, data);
} |
總結:單個線程池對象不共享方式在管理多線程時是以線程池對象中的異步隊列爲中心,新創建的線程或做完任務的線程並不釋放,讓它調用異步隊列的pop接口進入等待狀態,而在使用喚醒線程池中的線程就是調用異步隊列的push接口。
以上對於理解線程池的實現已經足夠,多個線程池對象共享線程方式和具體線程池的銷燬的技巧,在這裏就不討論了。