python之多線程


進(jìn)程的概念:以一個整體的形式暴露給操作系統(tǒng)管理,里面包含各種資源的調(diào)用。 對各種資源管理的集合就可以稱為進(jìn)程。
線程的概念:是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位。本質(zhì)上就是一串指令的集合。

進(jìn)程和線程的區(qū)別:
1、線程共享內(nèi)存空間,進(jìn)程有獨立的內(nèi)存空間。
2、線程啟動速度快,進(jìn)程啟動速度慢。注意:二者的運行速度是無法比較的。
3、線程是執(zhí)行的指令集,進(jìn)程是資源的集合
4、兩個子進(jìn)程之間數(shù)據(jù)不共享,完全獨立。同一個進(jìn)程下的線程共享同一份數(shù)據(jù)。
5、創(chuàng)建新的線程很簡單,創(chuàng)建新的進(jìn)程需要對他的父進(jìn)程進(jìn)行一次克隆。
6、一個線程可以操作(控制)同一進(jìn)程里的其他線程,但是進(jìn)程只能操作子進(jìn)程
7、同一個進(jìn)程的線程可以直接交流,兩個進(jìn)程想要通信,必須通過一個中間代理來實現(xiàn)。
8、對于線程的修改,可能會影響到其他線程的行為。但是對于父進(jìn)程的修改不會影響到子進(jìn)程。


  • 實例一:最簡單的多線程
    在python3中,我們使用threading模塊來支持多線程。
    t1=threading.Thread(target=run,args=("t1",)) 創(chuàng)建一個線程實例
    t1.start() 啟動這個線程實例
    t1.join() 不加join的話,主線程和子線程完全是并行的,加了join主線程得等這個子線程執(zhí)行完畢,才能繼續(xù)往下走。這樣才能得到這個程序真正的運行時間。
    觀察實驗結(jié)果,我們得到原本需要4s左右的執(zhí)行時間變成了2,秒,證明了多線程的高效率。
import threading,time
def run(n):
    print("task  ",n)
    time.sleep(2)

start_time=time.time()
t1=threading.Thread(target=run,args=("t1",))
t2=threading.Thread(target=run,args=("t2",))

t1.start()
t2.start()
t1.join()
t2.join()
print(time.time()-start_time)


F:\anaconda\python.exe F:/web/s14/進(jìn)程、線程/noke.py
task   t1
task   t2
2.0011146068573

  • 實例二:繼承式調(diào)用多線程
    創(chuàng)建一個新的類來支持多線程,繼承了threading.Thread類
import threading,time
class MyThread(threading.Thread):
    def __init__(self,n,sleep_time):
        super(MyThread, self).__init__()
        self.n=n
        self.sleeptime=sleep_time
    def run(self):
        print("run task",self.n)
        time.sleep(2)
        print("task done,",self.n)


t1=MyThread("t1",2)
t2=MyThread("t2",4)


t1.start()
t2.start()
t1.join()  #wait()  第一個線程執(zhí)行完畢后再執(zhí)行第二個線程
t2.join()




F:\anaconda\python.exe F:/web/s14/進(jìn)程、線程/threading_ex2.py
run task t1
run task t2
task done, t1
task done, t2
main thread!
  • 實例三: 一次性啟動多個線程

第一個程序,使用循環(huán)來創(chuàng)建線程,但是這個程序中一共有51個線程,我們創(chuàng)建了50個線程,但是還有一個程序本身的線程,是主線程。這51個線程是并行的。注意:這個程序中是主線程啟動了子線程。

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)


for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t.start()

相比上個程序,這個程序多了一步計算時間,但是我們觀察結(jié)果會發(fā)現(xiàn),程序顯示的執(zhí)行時間只有0.007秒,這是因為最后一個print函數(shù)它存在于主線程,而整個程序主線程和所有子線程是并行的,那么可想而知,在子線程還沒有執(zhí)行完畢的時候print函數(shù)就已經(jīng)執(zhí)行了,總的來說,這個時間只是執(zhí)行了一個線程也就是主線程所用的時間。

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)

start_time=time.time()
for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t.start()


print("cost  :" ,time.time()-start_time)


實驗部分結(jié)果:
cost  : 0.00700068473815918

接下來這個程序,吸取了上面這個程序的缺點,創(chuàng)建了一個列表,把所有的線程實例都存進(jìn)去,然后使用一個for循環(huán)依次對線程實例調(diào)用join方法,這樣就可以使得主線程等待所創(chuàng)建的所有子線程執(zhí)行完畢才能往下走。注意實驗結(jié)果:和兩個線程的結(jié)果都是兩秒多一點

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)

start_time=time.time()
t_obj=[]
for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t_obj.append(t)
    t.start()

for t in t_obj:
    t.join()


print("cost  :" ,time.time()-start_time)

部分實驗結(jié)果:
cost  : 2.0061144828796387

注意觀察實驗結(jié)果,并沒有執(zhí)行打印task has done,并且程序執(zhí)行時間極其短。
這是因為在主線程啟動子線程前把子線程設(shè)置為守護(hù)線程。
只要主線程執(zhí)行完畢,不管子線程是否執(zhí)行完畢,就結(jié)束。但是會等待非守護(hù)線程執(zhí)行完畢
主線程退出,守護(hù)線程全部強制退出?;实鬯懒耍腿艘哺吃?br> 應(yīng)用的場景 : socket-server

import threading
import time
def run(n):
    print("task",n)
    time.sleep(2)
    print("task has done!")     
start_time=time.time()
for i in range(50):
    t=threading.Thread(target=run,args=("t-%s"%i,))
    t.setDaemon(True)  #把當(dāng)前線程設(shè)置為守護(hù)線程,一定在start前設(shè)置
    t.start()
print(threading.current_thread(),threading.active_count())
print(time.time()-start_time)  

部分實驗結(jié)果:
task t-43
task t-44
task t-45
task t-46
task t-47
task t-48
task t-49
<_MainThread(MainThread, started 5940)> 51
0.0060002803802490234

  • 實例四:線程鎖(互斥鎖Mutex)
    介紹一下python的GIL,全局解釋器鎖。并不是python的特性,是實現(xiàn)python解析器的時候引入的概念。這個鎖是為了保證同一份數(shù)據(jù)不能被多個線程同時修改。因為cpython是使用c封裝的,所以線程也是用c語言實現(xiàn)的,在和cpu交互的時候使用的c接口。(java,c++等語言的自己實現(xiàn)的線程,所以自己可以直接控制cpu),而python就創(chuàng)造了一個全局解釋器鎖,來保證同一份數(shù)據(jù)不能被多個線程同時修改。
    所以,這就造成了python的一個缺陷,無論多少核的機(jī)器,同一時刻只能有一個線程在執(zhí)行。jpython沒有這個問題。
    python的未來是pypy。


注意:gil只是為了減低程序開發(fā)復(fù)雜度。但是在2.幾的版本上,需要加用戶態(tài)的鎖(gil的缺陷)而在3點幾的版本上,加鎖不加鎖都一樣。

import time
import threading
def run():
    lock.acquire()    #修改數(shù)據(jù)前加鎖
    global num
    num +=1
    lock.release()    #修改完后釋放
lock=threading.Lock()
num=0
t_objs = []
for i in range(1000):
    t=threading.Thread(target=run)
    t.start()
    t_objs.append(t)
for t in t_objs:  #循環(huán)線程實例列表,等待子線程執(zhí)行完畢
    t.join()
print(num)         
  • 實例五:Rlock遞歸鎖
    大鎖中包含兩個并行的子鎖。
    普通的鎖,在多個鎖的情況下,無法找到對應(yīng)的鑰匙,而使用Rlock可以,類似于字典的原理。
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()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)


if __name__ == '__main__':

    num, num2 = 0, 0
    lock = threading.RLock()  #RLOCK
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()

while threading.active_count() != 1:
    pass
else:
    print('----all threads done---')
    print(num, num2)
  • 實例六:信號量
    互斥鎖,同時允許一個線程更改數(shù)據(jù),而信號量同時允許一定數(shù)量的線程更改數(shù)據(jù)
import threading, time


def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" % n)
    semaphore.release()


if __name__ == '__main__':


    semaphore = threading.BoundedSemaphore(5)  # 最多允許5個線程同時運行
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass
else:
    print('----all threads done---')

  • 實例七:event
    通過event來實現(xiàn)兩個或者多個線程之間的交互。
    通過全局變量的設(shè)置
    下面這個例子,一個紅綠燈線程,多個車線程,車輛按紅綠燈規(guī)則行駛。
import time,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()
            print("\033[42;1mgreen light is on ...\033[0m")
            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():
            print("[%s] running ......"%name )
            time.sleep(1)
        else:
            print("[%s]  sees red light.."%name)
            event.wait()
            print("[%s] green light is on, start going...")




light = threading.Thread(target=lighter,)
light.start()


car1=threading.Thread(target=car,args=("tesla",))
car1.start()
  • 實例八:queue隊列
    隊列:非常重要
    功能:
    1、提高效率
    2、完成了程序的解耦
    可以理解為一個容器,這個容器里面存放數(shù)據(jù)
    既然有列表、元組這種容器為什么需要隊列?
    區(qū)別:列表中取數(shù)據(jù),相當(dāng)于復(fù)制一份數(shù)據(jù),而隊列中,數(shù)據(jù)只有一份,取走了就沒了
    先入先出,后進(jìn)先出,優(yōu)先級隊列

下面這個程序是一個典型的生產(chǎn)者消費者模型。
生產(chǎn)者消費者模型是經(jīng)典的在開發(fā)架構(gòu)中使用的模型
運維中的集群就是生產(chǎn)者消費者模型,生活中很多都是

import threading
import queue,time

q=queue.Queue(maxsize=10)
def Producer(name):
    count=1
    while True:
        q.put("骨頭 %s"%count)
        print("生產(chǎn)了骨頭",count)
        count+=1
        time.sleep(1)      
def Consumer(name):
    while True:
        print("[%s] 取到  [%s] 并且吃了它。。。"%(name,q.get()))
        time.sleep(1)
p=threading.Thread(target=Producer,args=('cq',))
c=threading.Thread(target=Consumer,args=("dog",))
c1=threading.Thread(target=Consumer,args=("cc",))

p.start()
c.start()
c1.start()

綜上:

那么,多線程的使用場景是什么?
python中的多線程實質(zhì)上是對上下文的不斷切換,可以說是假的多線程。而我們知道,io操作不占用cpu,計算占用cpu,那么python的多線程適合io操作密集的任務(wù),比如socket-server,那么cpu密集型的任務(wù),python怎么處理?python可以折中的利用計算機(jī)的多核:啟動八個進(jìn)程,每個進(jìn)程有一個線程。這樣就可以利用多進(jìn)程解決多核問題。

下一篇將介紹多進(jìn)程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 6,144評論 3 28
  • 進(jìn)程和線程 進(jìn)程 所有運行中的任務(wù)通常對應(yīng)一個進(jìn)程,當(dāng)一個程序進(jìn)入內(nèi)存運行時,即變成一個進(jìn)程.進(jìn)程是處于運行過程中...
    勝浩_ae28閱讀 5,256評論 0 23
  • 線程 操作系統(tǒng)線程理論 線程概念的引入背景 進(jìn)程 之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念,程序并不能單獨運行,只有...
    go以恒閱讀 1,787評論 0 6
  • 進(jìn)程與線程的區(qū)別 現(xiàn)在,多核CPU已經(jīng)非常普及了,但是,即使過去的單核CPU,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼...
    蘇糊閱讀 842評論 0 2
  • 顧名思義,進(jìn)程即正在執(zhí)行的一個過程。進(jìn)程是對正在運行程序的一個抽象。進(jìn)程的概念起源于操作系統(tǒng),是操作系統(tǒng)最核心的概...
    SlashBoyMr_wang閱讀 1,287評論 0 3

友情鏈接更多精彩內(nèi)容