<a href="http://python.jobbole.com/86406/" target="_blank">http://python.jobbole.com/86406/</a>
<a href="https://www.cnblogs.com/wupeiqi/articles/5040827.html" target="_blank">https://www.cnblogs.com/wupeiqi/articles/5040827.html</a>
<a href="https://www.cnblogs.com/tkqasn/p/5700281.html" target="_blank">https://www.cnblogs.com/tkqasn/p/5700281.html</a>
在此感謝前輩們的指導,以下均為純手打
一,程式,程序,線程概念的區分
在計算機中,程式不能單獨運作,必須把它配置設定到記憶體中,系統為它配置設定資源才能運作,而這種執行的程式就稱之為程序。
一句話總結:程式是指令的集合,它是程式的靜态描述文本;程序确是程式的一次執行活動,是動态概念
那麼,線程是什麼呢,它與程序有什麼關系呢,我們看幾個例子:
線程是執行上下文,它是CPU執行指令流所需的全部資訊。
1,假設你正在讀一本書,現在你想休息一下,但你希望能夠回來,從你停下來的确切地點恢複閱讀。一個實作的方法就是記下頁碼,行數和字數。是以你閱讀書的執行上下文是這3個數字。
2,如果你有一個室友,而且她使用相同的技巧,她可以在你不使用的時候拿書,然後從她停止閱讀的地方繼續閱讀。然後你可以收回它,從你原來的地方恢複它。
是以執行上下文(是以線程)由CPU寄存器的值組成。
綜上:線程不同于程序。線程是執行的上下文,而程序是與計算相關聯的一堆資源。一個程序可以有一個或多個線程(資源當中肯定不止一個上下文)。
關于程序與線程還有幾個重要的差別
1,與程序相關的資源包括記憶體頁(程序中的所有線程對記憶體具有相同的視圖)、檔案描述符(例如,打開套接字)和安全憑據(例如啟動程序的使用者ID)。
2,很容易建立新線程;新程序需要重複父程序。
3,線程可以對相同程序的線程進行相當的控制;程序隻能對子程序進行控制。
4,對主線程的更改(取消、優先級更改等)可能影響程序的其他線程的行為;對父程序的更改不會影響子程序。
二:有了程序,為什麼需要線程(拓展)
1,程序隻能在一個時間内幹一件事,但如果想同時幹兩件或多件事,程序就無能為力了。
2,程序在執行過程中,如果發生阻塞,那麼程序就會挂起,無法執行程式。
三,道路從線程開始:
threading 子產品提供的常用方法:
threading.currentThread(): 傳回目前的線程變量。
threading.enumerate(): 傳回一個包含正在運作的線程的list。正在運作指線程啟動後、結束前,不包括啟動前和終止後的線程。
threading.activeCount(): 傳回正在運作的線程數量,與len(threading.enumerate())有相同的結果。
threading 子產品提供的常量:
threading.TIMEOUT_MAX 設定threading全局逾時時間。
線程的方法:
start 線程準備就緒,等待CPU排程
setName 為線程設定名稱
getName 擷取線程名稱
setDaemon 設定為背景線程或前台線程(預設)
如果是背景線程,主線程執行過程中,背景線程也在進行,主線程執行完畢後,背景線程不論成功與否,均停止
如果是前台線程,主線程執行過程中,前台線程也在進行,主線程執行完畢後,等待前台線程也執行完成後,程式停止
join 逐個執行每個線程,執行完畢後繼續往下執行,該方法使得多線程變得無意義
run 線程被cpu排程後自動執行線程對象的run方法
接下來,圍繞這幾個方法,和線程的兩種調用方式進行講解
1,直接調用:
import threading
import time
def sayhi(num):
print("running on number:%s"%num)
time.sleep(3)
if __name__=='__main__':
t1=threading.Thread(target=sayhi,args=(1,))#線程執行個體,逗号必須加
t2 = threading.Thread(target=sayhi, args=(2,)) # 線程執行個體
t1.start()#啟動線程
t2.start()#啟動另一個線程
print(t1.getName())
print(t2.getName())
# 構造方法:
# Thread(group=None, target=None, name=None, args=(), kwargs={})
#
# group: 線程組,目前還沒有實作,庫引用中提示必須是None;
# target: 要執行的方法;
# name: 線程名;
# args / kwargs: 要傳入方法的參數。
再來看一個間接調用(繼承式調用):
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self): # 定義每個線程要運作的函數
print("running on number:%s" % self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
接下來對這兩個調用進行深究
一下子開五十個程序,看看時間多少
def run(n):
print("task",n)
time.sleep(2)
print("task done", n)
start_time=time.time()
for i in range(50):
t1=threading.Thread(target=run,args=("t-%s"%i,))
print("cost:",time.time()-start_time)
觀察它所消耗的時間是
摘取部分結果
task t-46
task t-47
task t-48
task t-49
------------all threads has finished
cost: 0.0070035457611083984
task done t-2
task done t-1
task done t-0
task done t-8
為什麼會出現這種情況呢,是因為主線程與子線程無關,不能在主線程中測子線程,因為主線程會執行自己的,,這裡可能說的有點抽象,emmmmn,其實我也是在把這遍代碼,碼完後,才有些懂得。
主線程可以看成是:
print("task",n
這段代碼集合(把run代碼中的sleep去掉了),他隻會執行自己的,不會等子線程的結束,是以将task打完後就把時間列印出來
那麼如何讓主線程等待呢:
print("task done",threading.current_thread())#傳回到目前線程的對象
t_objs=[]
t_objs.append(t1)
for t in t_objs:
t.join()#結束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主線程與子線程無關,是以不能在主線程中測子線程
部分結果展示:
task done <Thread(Thread-34, started 3940)>
task done <Thread(Thread-33, started 6752)>
task done <Thread(Thread-44, started 10280)>
task done <Thread(Thread-43, started 14988)>
task done <Thread(Thread-42, started 7344)>
task done <Thread(Thread-41, started 8984)>
task done <Thread(Thread-40, started 9972)>
task done <Thread(Thread-48, started 12672)>
task done <Thread(Thread-47, started 9012)>
task done <Thread(Thread-46, started 704)>
task done <Thread(Thread-45, started 7840)>
task done <Thread(Thread-49, started 10924)>
task done <Thread(Thread-50, started 14836)>
------------all threads has finished <_MainThread(MainThread, started 10780)>
cost: 2.0096378326416016
此時可以在主線程中測子線程了
注意,為什麼多線程會稱之為多線程,因為會用join方法,有的哥們兒這樣寫
t1.join()#結束
他就變成了順序的線程了,造成線程的阻塞了,應該竭力避免。
好了,我們再來看看守護(背景)線程的執行個體:
t1.setDaemon(True) # 把線程轉變成守護線程,不管分線程,主程序結束了,直接結束,不等分線程
# for t in t_objs:
# t.join()#結束
這個案例比前面幾個案例修改了什麼了。沒錯,當主線程結束的時候,守護線程也會跟着結束
t1.setDaemon(True) # 把線程轉變成守護線程,不管分線程,主程序結束了,直接結束,不等分線程t1.start()
這個是固定格式,改變位置容易出錯
以上是稍微簡單的些的概念,接下來看第二部分
線程鎖的引出:
一個程序可以啟動多個程序,多個線程共享父程序的記憶體空間,也就意味着每個線程可以通路一份資料,此時,如果有兩個線程要修改同一份資料,會出現什麼狀況?
會導緻資料出錯,這裡,我摘取前輩們的一部分解釋,因為相比于2.7版本,3中做了許多改進,前輩們的問題已經不會出現,目測是python源代碼中加了内置鎖。
<code>import</code> <code>time</code>
<code>import</code> <code>threading</code>
<code>def</code> <code>addNum():</code>
<code> </code><code>global</code> <code>num </code><code>#在每個線程中都擷取這個全局變量</code>
<code> </code><code>print</code><code>(</code><code>'--get num:'</code><code>,num )</code>
<code> </code><code>time.sleep(</code><code>1</code><code>)</code>
<code> </code><code>num </code><code>-</code><code>=</code><code>1</code> <code>#對此公共變量進行-1操作</code>
<code>num </code><code>=</code> <code>100</code> <code>#設定一個共享變量</code>
<code>thread_list </code><code>=</code> <code>[]</code>
<code>for</code> <code>i </code><code>in</code> <code>range</code><code>(</code><code>100</code><code>):</code>
<code> </code><code>t </code><code>=</code> <code>threading.Thread(target</code><code>=</code><code>addNum)</code>
<code> </code><code>t.start()</code>
<code> </code><code>thread_list.append(t)</code>
<code>for</code> <code>t </code><code>in</code> <code>thread_list: </code><code>#等待所有線程執行完畢</code>
<code> </code><code>t.join()</code>
<code>print</code><code>(</code><code>'final num:'</code><code>, num )</code>
正常來講,這個num結果應該是0, 但在python 2.7上多運作幾次,會發現,最後列印出來的num結果不總是0,為什麼每次運作的結果不一樣呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操作, 由于2個線程是并發同時運作的,是以2個線程很有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再指派給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每個線程在要修改公共資料時,為了避免自己在還沒改完的時候别人也來修改此資料,可以給這個資料加一把鎖, 這樣其它線程想修改此資料時就必須等待你修改完畢并把鎖釋放掉後才能再通路此資料。
上面是我複制粘貼,原話,這裡就不作解釋了
我們來看一下怎樣避免前輩們的情況
def addNum():
global num#在每個線程中都獲得這個全局變量
print('--get num:',num)
time.sleep(1)
lock.acquire()#修改資料前加鎖
num+=1
lock.release() # 修改後釋放
print('--get num', num)
num=0
thread_list=[]
lock = threading.Lock() #生成全局鎖
for i in range(100):
t=threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num',num)
這是關于線程鎖的一個解釋,我們再來看一看關于線程鎖的另一個解釋
遞歸鎖(對不起,各位,我的遞歸鎖部分本來花了兩個小時準備的,但是,因為我是編輯文章,是以沒儲存到草稿中,現在隻能把重要部分講給大家)
import threading, time
def run1():
print("grab the first part data")
lock.acquire()
global num
num += 1
lock.release()
return num
def run2():
print("grab the second part data")
global num2
num2 += 1
return num2
def run3():
res = run1()#調用run1() run1()是一個方法
print('--------between run1 and run2-----')
res2 = run2()#執行run2
print(res, res2)
num, num2 = 0, 0
lock = threading.RLock()#有一把鎖
for i in range(10):
t = threading.Thread(target=run3)
while threading.active_count() != 1:#主線程也是一個線程
print(threading.active_count())
else:
print('----all threads done---')
print(num, num2)
信号量
所鎖隻能一個對資料進行更改,但信号量可以多個
import threading,time
semaphore.acquire()
print("run the thread:%s\n"%n)
print(num)
semaphore.release()
num=0
semaphore=threading.BoundedSemaphore(5)#最多允許5個線程同時進行
for i in range(22):
t=threading.Thread(target=run,args=(i,))
t.start()
while threading.active_count()!=1:
pass
print('---all threads done----')
以下分享三個案例
先來一個簡單的執行個體
def __init__(self, signal):
self.singal = signal
def run(self):
print("I am %s,I will sleep ..." % self.name)
self.singal.wait()
print("I am %s, I awake..." % self.name)
if __name__ == "__main__":
singal = threading.Event()
for t in range(0, 3):
thread = MyThread(singal)
thread.start()
print("main thread sleep 3 seconds... ")
singal.set()
關于紅綠燈的
event=threading.Event()
def lighter():
count=0
event.set() # 變綠燈
while True:
if count>5 and count <10:#這一步是改成紅燈
event.clear()
print("\033[41;1mred light is on....\033[0m")
elif count>10:
event.set()#變綠燈
count=0
else:
print("\033[42;1mgreen light is on....\033[0m")
time.sleep(1)
count+=1
def car(name):
if event.is_set(): #代表綠燈
time.sleep(0.1)
print("[%s] running..."%name)
print("[%s] sees red light , waiting...." %name)
event.wait()
print("\033[34;1m[%s] green light is on, start going...\033[0m" %name)
light = threading.Thread(target=lighter,)
light.start()
car1 = threading.Thread(target=car,args=("Tesla",))
car1.start()
#此處還有一個案例沒完成,一個線程與多個線程之間的通信
未完待續,最近幾天會推出線程與爬蟲結合的執行個體與模闆
本文轉自眉間雪 51CTO部落格,原文連結:http://blog.51cto.com/13348847/1983306,如需轉載請自行聯系原作者