python线程,进程,协程
資料來源:https://www.cnblogs.com/alex3714/articles/5230609.html
http://python.jobbole.com/86406/
https://www.cnblogs.com/wupeiqi/articles/5040827.html
https://www.cnblogs.com/tkqasn/p/5700281.html
在此感謝前輩們的指導,以下均為純手打
一,程序,進程,線程概念的區分
在計算機中,程序不能單獨運行,必須把它分配到內存中,系統為它分配資源才能運行,而這種執行的程序就稱之為進程。
一句話總結:程序是指令的集合,它是程序的靜態描述文本;進程確是程序的一次執行活動,是動態概念
那么,線程是什么呢,它與進程有什么關系呢,我們看幾個例子:
線程是執行上下文,它是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: 要傳入方法的參數。
再來看一個間接調用(繼承式調用):
import threading
import time
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()
接下來對這兩個調用進行深究
一下子開五十個進程,看看時間多少
import threading
import time
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,))
? ?t1.start()
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,其實我也是在把這遍代碼,碼完后,才有些懂得。
主線程可以看成是:
import threading
import time
def run(n):
? ?print("task",n
start_time=time.time()
for i in range(50):
? ?t1=threading.Thread(target=run,args=("t-%s"%i,))
? ?t1.start()
print("cost:",time.time()-start_time)
這段代碼集合(把run代碼中的sleep去掉了),他只會執行自己的,不會等子線程的結束,所以將task打完后就把時間打印出來
那么如何讓主線程等待呢:
import threading
import time
def run(n):
? ?print("task",n)
? ?time.sleep(2)
? ?print("task done",threading.current_thread())#返回到當前線程的對象
start_time=time.time()
t_objs=[]
for i in range(50):
? ?t1=threading.Thread(target=run,args=("t-%s"%i,))
? ?t1.start()
? ?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方法,有的哥們兒這樣寫
import threading
import time
def run(n):
? ?print("task",n)
? ?time.sleep(2)
? ?print("task done",threading.current_thread())#返回到當前線程的對象
start_time=time.time()
t_objs=[]
for i in range(50):
? ?t1=threading.Thread(target=run,args=("t-%s"%i,))
? ?t1.start()
? ?t_objs.append(t1)
? ?t1.join()#結束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主線程與子線程無關,所以不能在主線程中測子線程
他就變成了順序的線程了,造成線程的阻塞了,應該竭力避免。
好了,我們再來看看守護(后臺)線程的實例:
setDaemon ? 設置為后臺線程或前臺線程(默認)
? ? ? ? ? ? ? ? ? ?如果是后臺線程,主線程執行過程中,后臺線程也在進行,主線程執行完畢后,后臺線程不論成功與否,均停止
? ? ? ? ? ? ? ? ? ??如果是前臺線程,主線程執行過程中,前臺線程也在進行,主線程執行完畢后,等待前臺線程也執行完成后,程序停止
import threading
import time
def run(n):
? ?print("task",n)
? ?time.sleep(2)
? ?print("task done",threading.current_thread())#返回到當前線程的對象
start_time=time.time()
t_objs=[]
for i in range(50):
? ?t1=threading.Thread(target=run,args=("t-%s"%i,))
? ?t1.setDaemon(True) ?# 把線程轉變成守護線程,不管分線程,主進程結束了,直接結束,不等分線程
? ?t1.start()
? ?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)#主線程與子線程無關,所以不能在主線程中測子線程
這個案例比前面幾個案例修改了什么了。沒錯,當主線程結束的時候,守護線程也會跟著結束
t1.setDaemon(True) ?# 把線程轉變成守護線程,不管分線程,主進程結束了,直接結束,不等分線程t1.start()
這個是固定格式,改變位置容易出錯
以上是稍微簡單的些的概念,接下來看第二部分
線程鎖的引出:
一個進程可以啟動多個進程,多個線程共享父進程的內存空間,也就意味著每個線程可以訪問一份數據,此時,如果有兩個線程要修改同一份數據,會出現什么狀況?
會導致數據出錯,這里,我摘取前輩們的一部分解釋,因為相比于2.7版本,3中做了許多改進,前輩們的問題已經不會出現,目測是python源代碼中加了內置鎖。
import?time import?threading def?addNum(): ????global?num?#在每個線程中都獲取這個全局變量 ????print('--get num:',num ) ????time.sleep(1) ????num??-=1?#對此公共變量進行-1操作 num?=?100??#設定一個共享變量 thread_list?=?[] 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 )正常來講,這個num結果應該是0, 但在python 2.7上多運行幾次,會發現,最后打印出來的num結果不總是0,為什么每次運行的結果不一樣呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操作, 由于2個線程是并發同時運行的,所以2個線程很有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量后,結果就都是99。那怎么辦呢? 很簡單,每個線程在要修改公共數據時,為了避免自己在還沒改完的時候別人也來修改此數據,可以給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢并把鎖釋放掉后才能再訪問此數據。?
上面是我復制粘貼,原話,這里就不作解釋了
我們來看一下怎樣避免前輩們的情況
import time
import threading
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")
? ?lock.acquire()
? ?global num2
? ?num2 += 1
? ?lock.release()
? ?return num2
def run3():
? ?lock.acquire()
? ?res = run1()#調用run1() run1()是一個方法
? ?print('--------between run1 and run2-----')
? ?res2 = run2()#執行run2
? ?lock.release()
? ?print(res, res2)
num, num2 = 0, 0
lock = threading.RLock()#有一把鎖
for i in range(10):
? ?t = threading.Thread(target=run3)
? ?t.start()
while threading.active_count() != 1:#主線程也是一個線程
? ?print(threading.active_count())
else:
? ?print('----all threads done---')
? ?print(num, num2)
信號量
所鎖只能一個對數據進行更改,但信號量可以多個
import threading,timedef run(n):
? ?semaphore.acquire()
? ?time.sleep(1)
? ?global num
? ?num+=1
? ?print("run the thread:%s\n"%n)
? ?print(num)
? ?semaphore.release()
if __name__=='__main__':
? ?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
else:
? ?print('---all threads done----')
以下分享三個案例
先來一個簡單的實例
import threadingimport time
class MyThread(threading.Thread):
? ?def __init__(self, signal):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?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... ")
? ?time.sleep(3)
? ?singal.set()
關于紅綠燈的
import timeimport threading
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):
? ?while True:
? ? ? ?if event.is_set(): #代表綠燈
? ? ? ? ? ?time.sleep(0.1)
? ? ? ? ? ?print("[%s] running..."%name)
? ? ? ?else:
? ? ? ? ? ?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,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的python线程,进程,协程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 请不要做浮躁的人——转给即将上路或者正在
- 下一篇: 2-Tenor AF AFT400-实战