python多線程(二)

來源:互聯網
上載者:User

標籤:一個人   過程   複製   環境   crt   寶典   oba   false   概念   

開啟線程的兩種方式
#方式一
from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)

if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.start()
   print(‘主線程‘)

 

#方式二
from threading import Thread
import time
class Sayhi(Thread):
   def __init__(self,name):
       super().__init__()
       self.name=name
   def run(self):
       time.sleep(2)
       print(‘%s say hello‘ % self.name)

if __name__ == ‘__main__‘:
   t = Sayhi(‘egon‘)
   t.start()
   print(‘主線程‘)

 

 在一個進程下開啟多個線程與在一個進程下開啟多個子進程的區別

誰的開啟速度快

from threading import Thread
from multiprocessing import Process
import os
def work():
   print(‘hello‘)
if __name__ == ‘__main__‘:
   #在主進程下開啟線程
   t=Thread(target=work)
   t.start()
   print(‘主線程/主進程‘)
   ‘‘‘
   列印結果:
   hello
   主線程/主進程
   ‘‘‘

   #在主進程下開啟子進程
   t=Process(target=work)
   t.start()
   print(‘主線程/主進程‘)
   ‘‘‘
   列印結果:
   主線程/主進程
   hello
   ‘‘‘

 

瞅一瞅pid

from threading import Thread
from multiprocessing import Process
import os

def work():
   print(‘hello‘,os.getpid())
if __name__ == ‘__main__‘:
   #part1:在主進程下開啟多個線程,每個線程都跟主進程的pid一樣
   t1=Thread(target=work)
   t2=Thread(target=work)
   t1.start()
   t2.start()
   print(‘主線程/主進程pid‘,os.getpid())
   #part2:開多個進程,每個進程都有不同的pid
   p1=Process(target=work)
   p2=Process(target=work)
   p1.start()
   p2.start()
   print(‘主線程/主進程pid‘,os.getpid())

 

同一進程內的線程共用該進程的資料?

from  threading import Thread
from multiprocessing import Process
import os
def work():
   global n
   n=0
if __name__ == ‘__main__‘:
   # n=100
   # p=Process(target=work)
   # p.start()
   # p.join()
   # print(‘主‘,n) #毫無疑問子進程p已經將自己的全域的n改成了0,但改的僅僅是它自己的,查看父進程的n仍然為100
   n=1
   t=Thread(target=work)
   t.start()
   t.join()
   print(‘主‘,n) #查看結果為0,因為同一進程內的線程之間共用進程內的資料

 

線程相關的其他方法
Thread執行個體對象的方法
 # isAlive(): 返回線程是否活動的。
 # getName(): 返回線程名。
 # setName(): 設定線程名。
threading模組提供的一些方法:
 # threading.currentThread(): 返回當前的線程變數。
 # threading.enumerate(): 返回一個包含正在啟動並執行線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。
 # threading.activeCount(): 返回正在啟動並執行線程數量,與len(threading.enumerate())有相同的結果。
複製代碼

 

主線程等待子線程結束

from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)

if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.start()
   t.join()
   print(‘主線程‘)
   print(t.is_alive())
   ‘‘‘
   egon say hello
   主線程
   False
   ‘‘‘

 

守護線程 

無論是進程還是線程,都遵循:守護xxx會等待主xxx運行完畢後被銷毀

需要強調的是:運行完畢並非終止運行

#1.對主進程來說,運行完畢指的是主進程代碼運行完畢

#2.對主線程來說,運行完畢指的是主線程所在的進程內所有非守護線程統統運行完畢,
主線程才算運行完畢

 

詳細解釋:

#1 主進程在其代碼結束後就已經算運行完畢了(守護進程在此時就被回收),然後主進程會
一直等非守護的子進程都運行完畢後回收子進程的資源(否則會產生殭屍進程),才會結束,

#2 主線程在其他非守護線程運行完畢後才算運行完畢(守護線程在此時就被回收)。因為
主線程的結束意味著進程的結束,進程整體的資源都將被回收,而進程
必須保證非守護線程都運行完畢後才能結束。

 

from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)
if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.setDaemon(True) #必須在t.start()之前設定
   t.start()
   print(‘主線程‘)
   print(t.is_alive())
   ‘‘‘
   主線程
   True
   ‘‘‘
 Python GIL(Global Interpreter Lock)

在Cpython解譯器中,同一個進程下開啟的多線程,同一時刻只能有一個線程執行,無法利用多核優勢

首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(文法)標準,但是可以用不同的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。像其中的JPython就沒有GIL。然而因為CPython是大部分環境下預設的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷。所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL

 

GIL本質就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質都一樣,都是將並發運行變成串列,以此來控制同一時間內共用資料只能被一個任務所修改,進而保證資料安全。

可以肯定的一點是:保護不同的資料的安全,就應該加不同的鎖。

要想瞭解GIL,首先確定一點:每次執行python程式,都會產生一個獨立的進程。例如python test.py,python aaa.py,python bbb.py會產生3個不同的python進程

在一個python的進程內,不僅有test.py的主線程或者由該主線程開啟的其他線程,還有解譯器開啟的記憶體回收等解譯器層級的線程,總之,所有線程都運行在這一個進程內,毫無疑問

如果多個線程的target=work,那麼執行流程是

多個線程先訪問到解譯器的代碼,即拿到執行許可權,然後將target的代碼交給解譯器的代碼去執行

解譯器的代碼是所有線程共用的,所以記憶體回收線程也可能訪問到解譯器的代碼而去執行,這就導致了一個問題:對於同一個資料100,可能線程1執行x=100的同時,而記憶體回收執行的是回收100的操作,解決這種問題沒有什麼高明的方法,就是加鎖處理,如的GIL,保證python解譯器同一時間只能執行一個任務的代碼

GIL與Lock

GIL保護的是解譯器級的資料,保護使用者自己的資料則需要自己加鎖處理,如

GIL與多線程

有了GIL的存在,同一時刻同一進程中只有一個線程被執行

聽到這裡,有的朋友立馬質問:進程可以利用多核,但是開銷大,而python的多線程開銷小,但卻無法利用多核優勢,也就是說python沒用了,php才是最牛逼的語言?

別著急啊,還沒講完呢。

要解決這個問題,我們需要在幾個點上達成一致:

#1. cpu到底是用來做計算的,還是用來做I/O的?
#2. 多cpu,意味著可以有多個核並行完成計算,所以多核提升的是計算效能
#3. 每個cpu一旦遇到I/O阻塞,仍然需要等待,所以多核對I/O操作沒什麼用處

 

一個工人相當於cpu,此時計算相當於工人在幹活,I/O阻塞相當於為工人幹活提供所需原材料的過程,工人幹活的過程中如果沒有原材料了,則工人幹活的過程需要停止,直到等待原材料的到來。

如果你的工廠乾的大多數任務都要有準備原材料的過程(I/O密集型),那麼你有再多的工人,意義也不大,還不如一個人,在等材料的過程中讓工人去幹別的活,

反過來講,如果你的工廠原材料都齊全,那當然是工人越多,效率越高

結論:

  對計算來說,cpu越多越好,但是對於I/O來說,再多的cpu也沒用

  當然對運行一個程式來說,隨著cpu的增多執行效率肯定會有所提高(不管提高幅度多大,總會有所提高),這是因為一個程式基本上不會是純計算或者純I/O,所以我們只能相對的去看一個程式到底是計算密集型還是I/O密集型,從而進一步分析python的多線程到底有無用武之地

#分析:
我們有四個任務需要處理,處理方式肯定是要玩出並發的效果,解決方案可以是:
方案一:開啟四個進程
方案二:一個進程下,開啟四個線程

#單核情況下,分析結果: 
  如果四個任務是計算密集型,沒有多核來並行計算,方案一徒增了建立進程的開銷,方案二勝
  如果四個任務是I/O密集型,方案一建立進程的開銷大,且進程的切換速度遠不如線程,方案二勝

#多核情況下,分析結果:
  如果四個任務是計算密集型,多核意味著並行計算,在python中一個進程中同一時刻只有一個
  線程執行用不上多核,方案一勝
  如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝
#結論:現在的電腦基本上都是多核,python對於計算密集型的任務開多線程的效率並不能帶來多大性
能上的提升,甚至不如串列(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。

 

多線程效能測試

計算密集型:多進程效率高

from multiprocessing import Process
from threading import Thread
import os,time
def work():
   res=0
   for i in range(100000000):
       res*=i


if __name__ == ‘__main__‘:
   l=[]
   print(os.cpu_count()) #本機為4核
   start=time.time()
   for i in range(4):
       p=Process(target=work) #耗時5s多
       p=Thread(target=work) #耗時18s多
       l.append(p)
       p.start()
   for p in l:
       p.join()
   stop=time.time()
   print(‘run time is %s‘ %(stop-start))

 

I/O密集型:多線程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
   time.sleep(2)
   print(‘===>‘)

if __name__ == ‘__main__‘:
   l=[]
   print(os.cpu_count()) #本機為4核
   start=time.time()
   for i in range(400):
       # p=Process(target=work) #耗時12s多,大部分時間耗費在建立進程上
       p=Thread(target=work) #耗時2s多
       l.append(p)
       p.start()
   for p in l:
       p.join()
   stop=time.time()
   print(‘run time is %s‘ %(stop-start))

 

應用:

多線程用於IO密集型,如socket,爬蟲,web
多進程用於計算密集型,如金融分析

 

 

識別圖中二維碼,歡迎關注python寶典

python多線程(二)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.