從零開始學Python(八):Python多線程和隊列

好久沒有更新博文啦,在家過春節已經變懶了-_-,不過答應你們更完這個python的入門系列,偶仍是會繼續努力的!另外祝願你們新年快樂,事事順心!java

線程的概念

咱們學習的不少編程語言,好比java,oc等,都會有線程這個概念.線程的用途很是的普遍,給咱們開發中帶來了不少的便利.主要用於一些串行或者並行的邏輯處理,好比點擊某個按鈕的時候,咱們能夠經過進度條來控制線程的運行時間,以便於更好的用於用戶的交互.python

每一個獨立的線程都包含一個程序的運行入口,順序的執行序列和一個程序運行的出口.線程必須在程序中存在,而不能獨立於程序運行!編程

每一個線程都有他本身的一組cpu儲存器,稱爲線程的上下文,該上下文反應了線程上次運行的cpu寄存器的狀態.指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程老是在進程獲得上下文運行,這些地址都用於標誌擁有線程的進程地址空間中的內存.數組

Python線程

在Python中,主要提供了thread和threading兩個線程模塊,thread模塊提供了最基礎的,最低級的線程函數,和一個簡單的鎖.threading模塊是thread模塊的封裝進階,提供了多樣的線程屬性和方法.下面咱們會對該兩個模塊逐個解析.安全

thread模塊(不推薦使用)

thread模塊經常使用的函數方法:多線程

函數名 描述
start_new_thread(function, args, kwargs=None) 產生一個新線程,function爲線程要運行的函數名,args是函數參數(tuple元組類型),kwargs爲可選參數
allocate_lock() 分配一個locktype類型的線程鎖對象
exit() 線程退出
_count() 返回線程數量,注意不包含主線程哦,因此在主線程運行該方法返回的是0
locked locktype 鎖,返回true爲已鎖
release() 釋放locktype對象鎖
acquire() 鎖定

下面咱們來舉個例子:編程語言

import thread,time


def loop1():
	print '線程個數-' + str(thread._count())
	i=0
	try:
    	while i < 100:
        	print i
        	time.sleep(1)
        	i = i + 1
	except Exception as e:
    	print e


thread.start_new_thread(loop1,())
複製代碼

運行上面代碼,你會發現loop1方法中的循環打印並無被調用,而是直接返回了一個異常:函數

Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr
複製代碼

這時你可能會一遍又一遍的檢查代碼,覺得是代碼錯了(沒錯,那我的就是我),其實咱們代碼自己是沒有錯誤的,是早期python的thread模塊一個缺陷(這個缺陷也是致使這個模塊被官方不推薦使用的主要緣由):當咱們在主線程中使用start_new_thread建立新的線程的時候,主線程沒法得知線程什麼時候結束,他不知道要等待多久,致使主線程已經執行完了,子線程卻還未完成,因而系統拋出了這個異常.oop

解決這個異常的方法有兩種:學習

1.讓主線程休眠足夠長的時間來等待子線程返回結果:

import thread,time


def loop1():
	print '線程個數-' + str(thread._count())
	i=0
	try:
    	while i < 100:
        	print i
        	time.sleep(1)
        	i = i + 1
	except Exception as e:
    	print e


thread.start_new_thread(loop1,())
time.sleep(1000)   #讓主線程休眠1000秒,足夠子線程完成
複製代碼

2.給線程加鎖(早期python線程使用通常處理)

import thread,time


def loop1(lock):
	print '線程個數-' + str(thread._count())
	i=0
	try:
    	while i < 100:
        	print i
        	time.sleep(1)
        	i = i + 1
	except Exception as e:
    	lock.release()
    	print e

	lock.release()    #執行完畢,釋放鎖


lock=thread.allocate_lock()   #獲取locktype對象
lock.acquire()   #鎖定

thread.start_new_thread(loop1,(lock,))
while lock.locked():    #等待線程鎖釋放
	pass
複製代碼

以上就是thread模塊的經常使用線程用法,咱們能夠看出,thread模塊提供的線程操做是極其有限的,使用起來很是的不靈活,下面咱們介紹他的同胞模塊threading.

threading模塊(推薦使用)

threading模塊是thread的完善,有一套成熟的線程操做方法,基本能完成咱們所需的全部線程操做

threading經常使用方法:

  • threading.currentThread(): 返回當前的線程變量。
  • threading.enumerate(): 返回一個包含正在運行的線程的list。 正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。
  • threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。
  • run(): 用以表示線程活動的方法。
  • start():啓動線程活動。
  • join([time]): 等待至線程停止。 這阻塞調用線程直至線程的join() 方法被調用停止-正常退出或者拋出未處理的異常-或者是可選的超時發生。
  • isAlive(): 返回線程是否活動的。
  • getName(): 返回線程名。
  • setName(): 設置線程名。

threading模塊建立線程有兩種方式:

1.直接經過初始化thread對象建立:

#coding=utf-8
import threading,time

def test():
	t = threading.currentThread()  # 獲取當前子線程對象
	print t.getName()  # 打印當前子線程名字

	i=0
	while i<10:
    	print i
    	time.sleep(1)
    	i=i+1




m=threading.Thread(target=test,args=(),name='循環子線程')   #初始化一個子線程對象,target是執行的目標函數,args是目標函數的參數,name是子線程的名字
m.start()
t=threading.currentThread()   #獲取當前線程對象,這裏實際上是主線程
print t.getName()   #打印當前線程名字,實際上是主線程名字
複製代碼

能夠看到打印結果:

循環子線程
MainThread
0
1
2
3
4
5
6
7
8
複製代碼

2.經過基礎thread類來建立

import threading,time
class myThread (threading.Thread):   #建立一個自定義線程類mythread,繼承Thread

def __init__(self,name):
    """
    從新init方法
    :param name: 線程名
    """
    super(myThread, self).__init__(name=name)
    # self.lock=lock

    print '線程名'+name

def run(self):
    """
    從新run方法,這裏面寫咱們的邏輯
    :return:
    """
    i=0
    while i<10:
        print i
        time.sleep(1)
        i=i+1


if __name__=='__main__':
	t=myThread('mythread')
	t.start()
複製代碼

輸出:

線程名線程
0
1
2
3
4
5
6
7
8
9
複製代碼

線程同步

若是兩個線程同時訪問同一個數據的時候,可能會出現沒法預料的結果,這時候咱們就要用到線程同步的概念.

上面咱們講到thread模塊的時候,已經使用了線程鎖的概念,thread對象的Lock和Rlock能夠實現簡單的線程同步,這兩個對象都有acquire方法和release方法,對於那些須要每次只容許一個線程操做的數據,能夠將其操做放到acquire和release方法之間.

下面咱們來舉例說明,咱們須要實現3個線程同時訪問一個全局變量,而且改變這個變量:

1.不加鎖的狀況
import threading,time

lock=threading.Lock()   #全局的鎖對象
temp=0    #咱們要多線程訪問的全局屬性

class myThread (threading.Thread):   #建立一個自定義線程類mythread,繼承Thread

	def __init__(self,name):
    	"""
    	從新init方法
    	:param name: 線程名
    	"""
    	super(myThread, self).__init__(name=name)
    	# self.lock=lock

    	print '線程名'+name

	def run(self):
    	"""
    	從新run方法,這裏面寫咱們的邏輯
    	:return:
    	"""
    	global temp,lock

    	i=0
    	while i<2:   #這裏循環兩次累加全局變量,目的是增長出錯的機率

        	temp=temp+1  #在子線程中實現對全局變量加1

        	print self.name+'--temp=='+str(temp)
        	i=i+1

	if __name__=='__main__':


		t1=myThread('線程1')
		t2=myThread('線程2')
		t3=myThread('線程3')

		#建立三個線程去執行訪問
		t1.start()
		t2.start()
		t3.start()
複製代碼

執行結果(因爲程序運行很快,你多運行幾回就可能會出現如下結果): 咱們能夠發現,線程1和線程2同時訪問到了變量,致使打印出現對等狀況

線程名線程1
線程名線程2
線程名線程3
線程1--temp==1線程2--temp==2
線程1--temp==3

線程2--temp==4
線程3--temp==5
線程3--temp==6
複製代碼
2.加鎖狀況
import threading,time

lock=threading.Lock()   #全局的鎖對象
temp=0    #咱們要多線程訪問的全局屬性

class myThread (threading.Thread):   #建立一個自定義線程類mythread,繼承Thread

	def __init__(self,name):
    	"""
    	從新init方法
    	:param name: 線程名
    	"""
    	super(myThread, self).__init__(name=name)
    	# self.lock=lock

    	print '線程名'+name

	def run(self):
    	"""
    	從新run方法,這裏面寫咱們的邏輯
    	:return:
    	"""
    	global temp,lock

    	if lock.acquire():  #這裏線程進來訪問變量的時候,鎖定變量
        	i = 0
        	while i < 2:  # 這裏循環兩次累加全局變量,目的是增長出錯的機率

            	temp = temp + 1  # 在子線程中實現對全局變量加1

            	print self.name + '--temp==' + str(temp)
            	i = i + 1

        	lock.release()  #訪問完畢,釋放鎖讓另外的線程訪問



if __name__=='__main__':


	t1=myThread('線程1')
	t2=myThread('線程2')
	t3=myThread('線程3')

	#建立三個線程去執行訪問
	t1.start()
	t2.start()
	t3.start()
複製代碼

運行結果(無論運行多少次,都不會出現同時訪問的狀況):

線程名線程1
線程名線程2
線程名線程3
線程1--temp==1
線程1--temp==2
線程2--temp==3
線程2--temp==4
線程3--temp==5
線程3--temp==6
複製代碼

線程同步不少地方都會用到,好比搶票,抽獎,咱們須要對一些資源進行鎖定,以防止多線程訪問的時候出現不可預知的狀況.

線程隊列

python中的隊列用到了Queue模塊,該模塊提供了同步的,安全的對序列,包括FIFO(先入先出)隊列Queue,LIFO(後入先出)隊列LifoQueue,和優先級隊列PriorityQueue.這些隊列都實現了鎖原語,可以在多線程中直接使用。可使用隊列來實現線程間的通訊

Queue模塊中的經常使用方法:

  • Queue.qsize() 返回隊列的大小
  • Queue.empty() 若是隊列爲空,返回True,反之False
  • Queue.full() 若是隊列滿了,返回True,反之False
  • Queue.full 與 maxsize 大小對應
  • Queue.get([block[, timeout]])獲取隊列,timeout等待時間
  • Queue.get_nowait() 至關Queue.get(False)
  • Queue.put(item) 寫入隊列,timeout等待時間
  • Queue.put_nowait(item) 至關Queue.put(item, False)
  • Queue.task_done() 在完成一項工做以後,Queue.task_done()函數向任務已經完成的隊列發送一個信號
  • Queue.join() 實際上意味着等到隊列爲空,再執行別的操做

例子:
tags=['one','tow','three','four','five','six']

q=Queue.LifoQueue()   #先入先出隊列
for t in tags:
q.put(t)   #將數組數據加入隊列
for i in range(6):
	print q.get()    #取出操做能夠放在不一樣的線程中,不會出現同步的問題
複製代碼

結果:

six
five
four
three
tow
one
複製代碼

Q&A

這章的多線程就到這裏了,咱們主要講述了他的基本用法,更多的用法咱們能夠在之後的開發過程當中,根據本身邏輯去設計.

相關文章
相關標籤/搜索