很久沒有更新博文啦,在家過春節已經變懶了-_-,不過答應大家更完這個python的入門系列,偶還是會繼續努力的!另外祝願大家新年快樂,事事順心!
執行緒的概念
我們學習的很多程式語言,比如java,oc等,都會有執行緒這個概念.執行緒的用途非常的廣泛,給我們開發中帶來了很多的便利.主要用於一些序列或者並行的邏輯處理,比如點選某個按鈕的時候,我們可以通過進度條來控制執行緒的執行時間,以便於更好的用於使用者的互動.
每個獨立的執行緒都包含一個程式的執行入口,順序的執行序列和一個程式執行的出口.執行緒必須在程式中存在,而不能獨立於程式執行!
每個執行緒都有他自己的一組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建立新的執行緒的時候,主執行緒無法得知執行緒何時結束,他不知道要等待多久,導致主執行緒已經執行完了,子執行緒卻還未完成,於是系統丟擲了這個異常.
解決這個異常的方法有兩種:
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
這章的多執行緒就到這裡了,我們主要講述了他的基本用法,更多的用法我們可以在以後的開發過程中,根據自己邏輯去設計.