線程池的研究及實現

線程池的研究及實現

什麼是線程池?

諸如web服務器、數據庫服務器、文件服務器和郵件服務器等許多服務器應用都面向處理來自某些遠程來源的大量短小的任務。構建服務器應用程序的一個過於簡單的模型是:每當一個請求到達就創建一個新的服務對象,然後在新的服務對象中爲請求服務。但當有大量請求併發訪問時,服務器不斷的創建和銷燬對象的開銷很大。所以提高服務器效率的一個手段就是儘可能減少創建和銷燬對象的次數,特別是一些很耗資源的對象創建和銷燬,這樣就引入了“池”的概念,“池”的概念使得人們可以定製一定量的資源,然後對這些資源進行復用,而不是頻繁的創建和銷燬。

線程池是預先創建線程的一種技術。線程池在還沒有任務到來之前,創建一定數量的線程,放入空閒隊列中。這些線程都是處於睡眠狀態,即均爲啓動,不消耗CPU,而只是佔用較小的內存空間。當請求到來之後,緩衝池給這次請求分配一個空閒線程,把請求傳入此線程中運行,進行處理。當預先創建的線程都處於運行狀態,即預製線程不夠,線程池可以自由創建一定數量的新線程,用於處理更多的請求。當系統比較閒的時候,也可以通過移除一部分一直處於停用狀態的線程。

線程池的注意事項

雖然線程池是構建多線程應用程序的強大機制,但使用它並不是沒有風險的。在使用線程池時需注意線程池大小與性能的關係,注意併發風險、死鎖、資源不足和線程泄漏等問題。

(1)線程池大小。多線程應用並非線程越多越好,需要根據系統運行的軟硬件環境以及應用本身的特點決定線程池的大小。一般來說,如果代碼結構合理的話,線程數目與CPU 數量相適合即可。如果線程運行時可能出現阻塞現象,可相應增加池的大小;如有必要可採用自適應算法來動態調整線程池的大小,以提高CPU 的有效利用率和系統的整體性能。

(2)併發錯誤。多線程應用要特別注意併發錯誤,要從邏輯上保證程序的正確性,注意避免死鎖現象的發生。

(3)線程泄漏。這是線程池應用中一個嚴重的問題,當任務執行完畢而線程沒能返回池中就會發生線程泄漏現象。

簡單線程池的設計

一個典型的線程池,應該包括如下幾個部分:
1、線程池管理器(ThreadPool),用於啓動、停用,管理線程池
2、工作線程(WorkThread),線程池中的線程
3、請求接口(WorkRequest),創建請求對象,以供工作線程調度任務的執行
4、請求隊列(RequestQueue),用於存放和提取請求
5、結果隊列(ResultQueue),用於存儲請求執行後返回的結果

線程池管理器,通過添加請求的方法(putRequest)向請求隊列(RequestQueue)添加請求,這些請求事先需要實現請求接口,即傳遞工作函數、參數、結果處理函數、以及異常處理函數。之後初始化一定數量的工作線程,這些線程通過輪詢的方式不斷查看請求隊列(RequestQueue),只要有請求存在,則會提取出請求,進行執行。然後,線程池管理器調用方法(poll)查看結果隊列(resultQueue)是否有值,如果有值,則取出,調用結果處理函數執行。通過以上講述,不難發現,這個系統的核心資源在於請求隊列和結果隊列,工作線程通過輪詢requestQueue獲得人物,主線程通過查看結果隊列,獲得執行結果。因此,對這個隊列的設計,要實現線程同步,以及一定阻塞和超時機制的設計,以防止因爲不斷輪詢而導致的過多cpu開銷。在本文中,將會用python語言實現,python的Queue,就是很好的實現了對線程同步機制。

使用Python實現:

複製代碼
#-*-encoding:utf-8-*-
'''
Created on 2012-3-9
@summary: 線程池
@contact: mailto:[email protected]
@author: zhanglixin
'''
import sys
import threading
import Queue
import traceback

# 定義一些Exception,用於自定義異常處理

class NoResultsPending(Exception):
"""All works requests have been processed"""
pass

class NoWorkersAvailable(Exception):
"""No worket threads available to process remaining requests."""
pass

def _handle_thread_exception(request, exc_info):
"""默認的異常處理函數,只是簡單的打印"""
traceback.print_exception(*exc_info)

#classes

class WorkerThread(threading.Thread):
"""後臺線程,真正的工作線程,從請求隊列(requestQueue)中獲取work,
並將執行後的結果添加到結果隊列(resultQueue)
"""
def __init__(self,requestQueue,resultQueue,poll_timeout=5,**kwds):
threading.Thread.__init__(self,**kwds)
'''設置爲守護進行'''
self.setDaemon(True)
self._requestQueue = requestQueue
self._resultQueue = resultQueue
self._poll_timeout = poll_timeout
'''設置一個flag信號,用來表示該線程是否還被dismiss,默認爲false'''
self._dismissed = threading.Event()
self.start()

def run(self):
'''每個線程儘可能多的執行work,所以採用loop,
只要線程可用,並且requestQueue有work未完成,則一直loop
'''
while True:
if self._dismissed.is_set():
break
try:
'''
Queue.Queue隊列設置了線程同步策略,並且可以設置timeout。
一直block,直到requestQueue有值,或者超時
'''
request = self._requestQueue.get(True,self._poll_timeout)
except Queue.Empty:
continue
else:
'''之所以在這裏再次判斷dimissed,是因爲之前的timeout時間裏,很有可能,該線程被dismiss掉了'''
if self._dismissed.is_set():
self._requestQueue.put(request)
break
try:
'''執行callable,講請求和結果以tuple的方式放入requestQueue'''
result = request.callable(*request.args,**request.kwds)
print self.getName()
self._resultQueue.put((request,result))
except:
'''異常處理'''
request.exception = True
self._resultQueue.put((request,sys.exc_info()))

def dismiss(self):
'''設置一個標誌,表示完成當前work之後,退出'''
self._dismissed.set()


class WorkRequest:
'''
@param callable_:,可定製的,執行work的函數
@param args: 列表參數
@param kwds: 字典參數
@param requestID: id
@param callback: 可定製的,處理resultQueue隊列元素的函數
@param exc_callback:可定製的,處理異常的函數
'''
def __init__(self,callable_,args=None,kwds=None,requestID=None,
callback=None,exc_callback=_handle_thread_exception):
if requestID == None:
self.requestID = id(self)
else:
try:
self.requestID = hash(requestID)
except TypeError:
raise TypeError("requestId must be hashable")
self.exception = False
self.callback = callback
self.exc_callback = exc_callback
self.callable = callable_
self.args = args or []
self.kwds = kwds or {}

def __str__(self):
return "WorkRequest id=%s args=%r kwargs=%r exception=%s" % \
(self.requestID,self.args,self.kwds,self.exception)

class ThreadPool:
'''
@param num_workers:初始化的線程數量
@param q_size,resq_size: requestQueue和result隊列的初始大小
@param poll_timeout: 設置工作線程WorkerThread的timeout,也就是等待requestQueue的timeout
'''
def __init__(self,num_workers,q_size=0,resq_size=0,poll_timeout=5):
self._requestQueue = Queue.Queue(q_size)
self._resultQueue = Queue.Queue(resq_size)
self.workers = []
self.dismissedWorkers = []
self.workRequests = {} #設置個字典,方便使用
self.createWorkers(num_workers,poll_timeout)

def createWorkers(self,num_workers,poll_timeout=5):
'''創建num_workers個WorkThread,默認timeout爲5'''
for i in range(num_workers):
self.workers.append(WorkerThread(self._requestQueue,self._resultQueue,poll_timeout=poll_timeout))

def dismissWorkers(self,num_workers,do_join=False):
'''停用num_workers數量的線程,並加入dismiss_list'''
dismiss_list = []
for i in range(min(num_workers,len(self.workers))):
worker = self.workers.pop()
worker.dismiss()
dismiss_list.append(worker)
if do_join :
for worker in dismiss_list:
worker.join()
else:
self.dismissedWorkers.extend(dismiss_list)

def joinAllDismissedWorkers(self):
'''join 所有停用的thread'''
#print len(self.dismissedWorkers)
for worker in self.dismissedWorkers:
worker.join()
self.dismissedWorkers = []

def putRequest(self,request ,block=True,timeout=None):
assert isinstance(request,WorkRequest)
assert not getattr(request,'exception',None)
'''當queue滿了,也就是容量達到了前面設定的q_size,它將一直阻塞,直到有空餘位置,或是timeout'''
self._requestQueue.put(request, block, timeout)
self.workRequests[request.requestID] = request

def poll(self,block = False):
while True:
if not self.workRequests:
raise NoResultsPending
elif block and not self.workers:
raise NoWorkersAvailable
try:
'''默認只要resultQueue有值,則取出,否則一直block'''
request , result = self._resultQueue.get(block=block)
if request.exception and request.exc_callback:
request.exc_callback(request,result)
if request.callback and not (request.exception and request.exc_callback):
request.callback(request,result)
del self.workRequests[request.requestID]
except Queue.Empty:
break

def wait(self):
while True:
try:
self.poll(True)
except NoResultsPending:
break

def workersize(self):
return len(self.workers)

def stop(self):
'''join 所有的thread,確保所有的線程都執行完畢'''
self.dismissWorkers(self.workersize(),True)
self.joinAllDismissedWorkers()
複製代碼

測試代碼:

複製代碼
#Test a demo

if __name__=='__main__':
import random
import time
import datetime
def do_work(data):
time.sleep(random.randint(1,3))
res = str(datetime.datetime.now()) + "" +str(data)
return res

def print_result(request,result):
print "---Result from request %s : %r" % (request.requestID,result)

main = ThreadPool(3)
for i in range(40):
req = WorkRequest(do_work,args=[i],kwds={},callback=print_result)
main.putRequest(req)
print "work request #%s added." % req.requestID

print '-'*20, main.workersize(),'-'*20

counter = 0
while True:
try:
time.sleep(0.5)
main.poll()
if(counter==5):
print "Add 3 more workers threads"
main.createWorkers(3)
print '-'*20, main.workersize(),'-'*20
if(counter==10):
print "dismiss 2 workers threads"
main.dismissWorkers(2)
print '-'*20, main.workersize(),'-'*20
counter+=1
except NoResultsPending:
print "no pending results"
break

main.stop()
print "Stop"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章