很久沒有更新博文啦,在家過春節(jié)已經(jīng)變懶了-_-,不過答應(yīng)大家更完這個(gè)python的入門系列,偶還是會(huì)繼續(xù)努力的!另外祝愿大家新年快樂,事事順心!
線程的概念
我們學(xué)習(xí)的很多編程語言,比如java,oc等,都會(huì)有線程這個(gè)概念.線程的用途非常的廣泛,給我們開發(fā)中帶來了很多的便利.主要用于一些串行或者并行的邏輯處理,比如點(diǎn)擊某個(gè)按鈕的時(shí)候,我們可以通過進(jìn)度條來控制線程的運(yùn)行時(shí)間,以便于更好的用于用戶的交互.
每個(gè)獨(dú)立的線程都包含一個(gè)程序的運(yùn)行入口,順序的執(zhí)行序列和一個(gè)程序運(yùn)行的出口.線程必須在程序中存在,而不能獨(dú)立于程序運(yùn)行!
每個(gè)線程都有他自己的一組cpu儲(chǔ)存器,稱為線程的上下文,該上下文反應(yīng)了線程上次運(yùn)行的cpu寄存器的狀態(tài).指令指針和堆棧指針寄存器是線程上下文中兩個(gè)最重要的寄存器,線程總是在進(jìn)程得到上下文運(yùn)行,這些地址都用于標(biāo)志擁有線程的進(jìn)程地址空間中的內(nèi)存.
Python線程
在Python中,主要提供了thread和threading兩個(gè)線程模塊,thread模塊提供了最基礎(chǔ)的,最低級(jí)的線程函數(shù),和一個(gè)簡單的鎖.threading模塊是thread模塊的封裝進(jìn)階,提供了多樣的線程屬性和方法.下面我們會(huì)對(duì)該兩個(gè)模塊逐個(gè)解析.
thread模塊(不推薦使用)
thread模塊常用的函數(shù)方法:
函數(shù)名 | 描述 |
---|---|
start_new_thread(function, args, kwargs=None) | 產(chǎn)生一個(gè)新線程,function為線程要運(yùn)行的函數(shù)名,args是函數(shù)參數(shù)(tuple元組類型),kwargs為可選參數(shù) |
allocate_lock() | 分配一個(gè)locktype類型的線程鎖對(duì)象 |
exit() | 線程退出 |
_count() | 返回線程數(shù)量,注意不包含主線程哦,所以在主線程運(yùn)行該方法返回的是0 |
locked | locktype 鎖,返回true為已鎖 |
release() | 釋放locktype對(duì)象鎖 |
acquire() | 鎖定 |
下面我們來舉個(gè)例子:
import thread,time
def loop1():
print '線程個(gè)數(shù)-' + 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,())
運(yùn)行上面代碼,你會(huì)發(fā)現(xiàn)loop1方法中的循環(huán)打印并沒有被調(diào)用,而是直接返回了一個(gè)異常:
Unhandled exception in thread started by
sys.excepthook is missing
lost sys.stderr
這時(shí)你可能會(huì)一遍又一遍的檢查代碼,以為是代碼錯(cuò)了(沒錯(cuò),那個(gè)人就是我),其實(shí)我們代碼本身是沒有錯(cuò)誤的,是早期python的thread模塊一個(gè)缺陷(這個(gè)缺陷也是導(dǎo)致這個(gè)模塊被官方不推薦使用的主要原因):當(dāng)我們?cè)谥骶€程中使用start_new_thread創(chuàng)建新的線程的時(shí)候,主線程無法得知線程何時(shí)結(jié)束,他不知道要等待多久,導(dǎo)致主線程已經(jīng)執(zhí)行完了,子線程卻還未完成,于是系統(tǒng)拋出了這個(gè)異常.
解決這個(gè)異常的方法有兩種:
1.讓主線程休眠足夠長的時(shí)間來等待子線程返回結(jié)果:
import thread,time
def loop1():
print '線程個(gè)數(shù)-' + 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 '線程個(gè)數(shù)-' + 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() #執(zhí)行完畢,釋放鎖
lock=thread.allocate_lock() #獲取locktype對(duì)象
lock.acquire() #鎖定
thread.start_new_thread(loop1,(lock,))
while lock.locked(): #等待線程鎖釋放
pass
以上就是thread模塊的常用線程用法,我們可以看出,thread模塊提供的線程操作是極其有限的,使用起來非常的不靈活,下面我們介紹他的同胞模塊threading.
threading模塊(推薦使用)
threading模塊是thread的完善,有一套成熟的線程操作方法,基本能完成我們所需的所有線程操作
threading常用方法:
- threading.currentThread(): 返回當(dāng)前的線程變量。
- threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list捕犬。 正在運(yùn)行指線程啟動(dòng)后、結(jié)束前,不包括啟動(dòng)前和終止后的線程。
- threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量赠法,與len(threading.enumerate())有相同的結(jié)果看尼。
- run(): 用以表示線程活動(dòng)的方法。
- start():啟動(dòng)線程活動(dòng)上煤。
- join([time]): 等待至線程中止。 這阻塞調(diào)用線程直至線程的join() 方法被調(diào)用中止-正常退出或者拋出未處理的異常-或者是可選的超時(shí)發(fā)生著淆。
- isAlive(): 返回線程是否活動(dòng)的劫狠。
- getName(): 返回線程名。
- setName(): 設(shè)置線程名永部。
threading模塊創(chuàng)建線程有兩種方式:
1.直接通過初始化thread對(duì)象創(chuàng)建:
#coding=utf-8
import threading,time
def test():
t = threading.currentThread() # 獲取當(dāng)前子線程對(duì)象
print t.getName() # 打印當(dāng)前子線程名字
i=0
while i<10:
print i
time.sleep(1)
i=i+1
m=threading.Thread(target=test,args=(),name='循環(huán)子線程') #初始化一個(gè)子線程對(duì)象,target是執(zhí)行的目標(biāo)函數(shù),args是目標(biāo)函數(shù)的參數(shù),name是子線程的名字
m.start()
t=threading.currentThread() #獲取當(dāng)前線程對(duì)象,這里其實(shí)是主線程
print t.getName() #打印當(dāng)前線程名字,其實(shí)是主線程名字
可以看到打印結(jié)果:
循環(huán)子線程
MainThread
0
1
2
3
4
5
6
7
8
2.通過基礎(chǔ)thread類來創(chuàng)建
import threading,time
class myThread (threading.Thread): #創(chuàng)建一個(gè)自定義線程類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
線程同步
如果兩個(gè)線程同時(shí)訪問同一個(gè)數(shù)據(jù)的時(shí)候,可能會(huì)出現(xiàn)無法預(yù)料的結(jié)果,這時(shí)候我們就要用到線程同步的概念.
上面我們講到thread模塊的時(shí)候,已經(jīng)使用了線程鎖的概念,thread對(duì)象的Lock和Rlock可以實(shí)現(xiàn)簡單的線程同步,這兩個(gè)對(duì)象都有acquire方法和release方法独泞,對(duì)于那些需要每次只允許一個(gè)線程操作的數(shù)據(jù),可以將其操作放到acquire和release方法之間.
下面我們來舉例說明,我們需要實(shí)現(xiàn)3個(gè)線程同時(shí)訪問一個(gè)全局變量,并且改變這個(gè)變量:
1.不加鎖的情況
import threading,time
lock=threading.Lock() #全局的鎖對(duì)象
temp=0 #我們要多線程訪問的全局屬性
class myThread (threading.Thread): #創(chuàng)建一個(gè)自定義線程類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: #這里循環(huán)兩次累加全局變量,目的是增加出錯(cuò)的概率
temp=temp+1 #在子線程中實(shí)現(xiàn)對(duì)全局變量加1
print self.name+'--temp=='+str(temp)
i=i+1
if __name__=='__main__':
t1=myThread('線程1')
t2=myThread('線程2')
t3=myThread('線程3')
#創(chuàng)建三個(gè)線程去執(zhí)行訪問
t1.start()
t2.start()
t3.start()
執(zhí)行結(jié)果(由于程序運(yùn)行很快,你多運(yùn)行幾次就可能會(huì)出現(xiàn)以下結(jié)果): 我們可以發(fā)現(xiàn),線程1和線程2同時(shí)訪問到了變量,導(dǎo)致打印出現(xiàn)對(duì)等情況
線程名線程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() #全局的鎖對(duì)象
temp=0 #我們要多線程訪問的全局屬性
class myThread (threading.Thread): #創(chuàng)建一個(gè)自定義線程類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(): #這里線程進(jìn)來訪問變量的時(shí)候,鎖定變量
i = 0
while i < 2: # 這里循環(huán)兩次累加全局變量,目的是增加出錯(cuò)的概率
temp = temp + 1 # 在子線程中實(shí)現(xiàn)對(duì)全局變量加1
print self.name + '--temp==' + str(temp)
i = i + 1
lock.release() #訪問完畢,釋放鎖讓另外的線程訪問
if __name__=='__main__':
t1=myThread('線程1')
t2=myThread('線程2')
t3=myThread('線程3')
#創(chuàng)建三個(gè)線程去執(zhí)行訪問
t1.start()
t2.start()
t3.start()
運(yùn)行結(jié)果(不管運(yùn)行多少次,都不會(huì)出現(xiàn)同時(shí)訪問的情況):
線程名線程1
線程名線程2
線程名線程3
線程1--temp==1
線程1--temp==2
線程2--temp==3
線程2--temp==4
線程3--temp==5
線程3--temp==6
線程同步很多地方都會(huì)用到,比如搶票,抽獎(jiǎng),我們需要對(duì)一些資源進(jìn)行鎖定,以防止多線程訪問的時(shí)候出現(xiàn)不可預(yù)知的情況.
線程隊(duì)列
python中的隊(duì)列用到了Queue模塊,該模塊提供了同步的,安全的對(duì)序列,包括FIFO(先入先出)隊(duì)列Queue苔埋,LIFO(后入先出)隊(duì)列LifoQueue懦砂,和優(yōu)先級(jí)隊(duì)列PriorityQueue.這些隊(duì)列都實(shí)現(xiàn)了鎖原語,能夠在多線程中直接使用组橄≤癖欤可以使用隊(duì)列來實(shí)現(xiàn)線程間的通信
Queue模塊中的常用方法:
- Queue.qsize() 返回隊(duì)列的大小
- Queue.empty() 如果隊(duì)列為空,返回True,反之False
- Queue.full() 如果隊(duì)列滿了玉工,返回True,反之False
- Queue.full 與 maxsize 大小對(duì)應(yīng)
- Queue.get([block[, timeout]])獲取隊(duì)列羽资,timeout等待時(shí)間
- Queue.get_nowait() 相當(dāng)Queue.get(False)
- Queue.put(item) 寫入隊(duì)列,timeout等待時(shí)間
- Queue.put_nowait(item) 相當(dāng)Queue.put(item, False)
- Queue.task_done() 在完成一項(xiàng)工作之后瓮栗,Queue.task_done()函數(shù)向任務(wù)已經(jīng)完成的隊(duì)列發(fā)送一個(gè)信號(hào)
- Queue.join() 實(shí)際上意味著等到隊(duì)列為空削罩,再執(zhí)行別的操作
例子:
tags=['one','tow','three','four','five','six']
q=Queue.LifoQueue() #先入先出隊(duì)列
for t in tags:
q.put(t) #將數(shù)組數(shù)據(jù)加入隊(duì)列
for i in range(6):
print q.get() #取出操作可以放在不同的線程中,不會(huì)出現(xiàn)同步的問題
結(jié)果:
six
five
four
three
tow
one
Q&A
這章的多線程就到這里了,我們主要講述了他的基本用法,更多的用法我們可以在以后的開發(fā)過程中,根據(jù)自己邏輯去設(shè)計(jì).