python之進程,線程

 

程序並不能單獨運行,只有將程序裝載到內存中,系統爲它分配資源才能運行,而這種執行的程序就稱之爲進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。java

在多道編程中,咱們容許多個程序同時加載到內存中,在操做系統的調度下,能夠實現併發地執行。這是這樣的設計,大大提升了CPU的利用率。進程的出現讓每一個用戶感受到本身獨享CPU,所以,進程就是爲了在CPU上實現多道編程而提出的python

有了進程爲何還要線程?

進程有不少優勢,它提供了多道編程,讓咱們感受咱們每一個人都擁有本身的CPU和其餘資源,能夠提升計算機的利用率。不少人就不理解了,既然進程這麼優秀,爲何還要線程呢?其實,仔細觀察就會發現進程仍是有不少缺陷的,主要體如今兩點上:編程

  • 進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。多線程

  • 進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行。併發

例如,咱們在使用qq聊天, qq作爲一個獨立進程若是同一時間只能幹一件事,那他如何實如今同一時刻 即能監聽鍵盤輸入、又能監聽其它人給你發的消息、同時還能把別人發的消息顯示在屏幕上呢?你會說,操做系統不是有分時麼?但個人親,分時是指在不一樣進程間的分時呀, 即操做系統處理一會你的qq任務,又切換到word文檔任務上了,每一個cpu時間片分給你的qq程序時,你的qq仍是隻能同時幹一件事呀。app

再直白一點, 一個操做系統就像是一個工廠,工廠裏面有不少個生產車間,不一樣的車間生產不一樣的產品,每一個車間就至關於一個進程,且你的工廠又窮,供電不足,同一時間只能給一個車間供電,爲了能讓全部車間都能同時生產,你的工廠的電工只能給不一樣的車間分時供電,可是輪到你的qq車間時,發現只有一個幹活的工人,結果生產效率極低,爲了解決這個問題,應該怎麼辦呢?。。。。沒錯,你確定想到了,就是多加幾個工人,讓幾我的工人並行工做,這每一個工人,就是線程!dom

什麼是線程(thread)?

線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務函數

進程想要執行,就必須先生成一個線程,讓線程執行,進程只是把資源整合,不能執行ui

全部再同一個進程裏的線程是共享同一塊內存空間的操作系統

進程與線程的區別?

啓動一個線程要比啓動進程快,可是運行速度是沒有辦法比的。

1,線程共享內存空間,進程的內存是獨立的

2,兩個子進程之間的數據的分開的,不是共享的,同一個進程中的兩個線程是共享的數據

3,同一個進程的線程之間能夠直接交流,進程之間想通訊必須經過一箇中間代理來實現

4,建立新線程很簡單,建立新進程須要對其父進程進行一次克隆

5,一個線程能夠控制和操做同一進程裏的其餘線程,可是進程只能操做子進程

 

 

多線程的實際例子:

#--------------------------這是一種多線程的寫法,直接調用--------------------
import threading
import time
def run(n):
print('task',n)
time.sleep(2)
t1 = threading.Thread(target=run,args=('t1',)) #target是標題的意思,調用哪一個方法,就寫哪一個方法名字,args('t1',)這是傳參數
t2 = threading.Thread(target=run,args=('t2',))
t1.start() #啓動線程
t2.start()
'''
這兩個線程是並行的,一共等2秒鐘
'''
#--------------------------這是另一種多線程的寫法,繼承式寫法--------------------
import threading
import time

class MyThreading(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n = n

def run(self): #必需要叫run 定義每一個線程要運行的函數
print('running task',self.n)
time.sleep(2)

t1 = MyThreading('t1')
t2 = MyThreading('t2')
t1.start()
t1.join() #至關於wait,python裏面有join
t2.start()
#--------------------------再次理解多線程運行的原理--------------------
import threading
import time

class MyThreading(threading.Thread):
def __init__(self,n,sleep_lime): #這裏把父類的構造函數重寫了,因此要重構一下,也就是繼承一下
threading.Thread.__init__(self) #這裏把父類的構造函數重寫了,因此要重構一下,也就是繼承一下
self.n = n
self.sleep_time = sleep_lime

def run(self): #必需要叫run 定義每一個線程要運行的函數,線程運行的時候,就是調用run,其餘的不調用,只是再這種寫法裏面
print('running task',self.n)
time.sleep(self.sleep_time)
print('task done',self.n)

t1 = MyThreading('t1',2)
t2 = MyThreading('t2',4)
t1.start() #調用了run方法
#t1.join() #至關於wait,python裏面有join,等待
t2.start() #調用了run方法
t1.join() #這是t1的線程等待,程序運行從上往下,運行到t1.join()的時候,等t1線程,此時t2線程繼續調用run方法運行,t1時間到了後,程序繼續從上往下走,運行print('main...........')
#等print('main...........')運行完畢,也意味着主程序(主線程)運行完成,此時t2的等待時間到了以後,就輸出t2調用run裏面的東西了
#t2.join()
print('main...........',threading.current_thread()) #這個threading.current_thread()是查看這句代碼是哪一個線程執行的
print(threading.active_count()) #threading.active_count()這個是查看當前線程活動的個數

#-----------------------------守護進程---------------------------------------------
主進程建立守護進程
其一:守護進程會在主進程代碼執行結束後就終止
其二:守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children
注意:進程之間是互相獨立的,主進程代碼運行結束,守護進程隨即終止
import time
def run(n):
print('task',n)
time.sleep(2)
print('task done',n)
#啓動50個線程
start_time = time.time()
for i in range(50):
t = threading.Thread(target=run,args=('t-%s'%i,))
t.setDaemon(True) #把當前線程設置爲守護線程,必定要再start()以前,程序會等主線程執行完畢,可是不會等守護線程執行完畢就退出
t.start()
print('all threading')
print('cost:',time.time()-start_time)

另一個例子:
from multiprocessing import Process
import time
import random

class Piao(Process):
def __init__(self,name):
self.name=name
super().__init__()
def run(self):
print('%s is piaoing' %self.name)
time.sleep(random.randrange(1,3))
print('%s is piao end' %self.name)


p=Piao('egon')
p.daemon=True #必定要在p.start()前設置,設置p爲守護進程,禁止p建立子進程,而且父進程代碼執行結束,p即終止運行
p.start()
print('主')


Python GIL(Global Interpreter Lock)全局解析器鎖

首先須要明確的一點是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對python多線程的影響,強烈推薦看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

GIL:多核的意義就是同時作不少事情,可是python中不是這樣的,不管你有幾核,python運行起來就是單個線程運行,叫假線程。這就叫全局解析器鎖
python其實就是調用操做系統原生的線程接口,只能啓一個。python調用一個線程的時候,就要把上下文關係傳給他。同一時間只能有一個線程去工做,
這是全局鎖控制的,java就能自定義執行。之後pypy會是重點,由於取消了這個限制,並且仍是計時編譯。

#----------------------------線程鎖(互斥鎖Mutex)--------------------------
#一個進程下能夠啓動多個線程,多個線程共享父進程的內存空間,也就意味着每一個線程能夠訪問同一份數據,此時,若是2個線程同時要修改同一份數據,會出現什麼情況?
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。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,
爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。
*注:不要在3.x上運行,不知爲何,3.x上的結果老是正確的,多是自動加了鎖
import time
import threading
def addNum():
global num # 在每一個線程中都獲取這個全局變量
print('--get num:', num)
time.sleep(1)
lock.acquire() # 修改數據前加鎖
num -= 1 # 對此公共變量進行-1操做
lock.release() # 修改後釋放
num = 100 # 設定一個共享變量
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 threadingimport timedef addNUM():    global num #在每一個線程中都獲取這個全局變量    print('--get num:',num)    time.sleep(1)    lock.acquire() #修改數據前加鎖    num -=1 #對此公共變量進行-1操做    lock.release()#修改後釋放num = 100thread_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('num:',num)那你又問了, 既然用戶程序已經本身有鎖了,那爲何C python還須要GIL呢?加入GIL主要的緣由是爲了下降程序的開發的複雜度,好比如今的你寫python不須要關心內存回收的問題,由於Python解釋器幫你自動按期進行內存回收,你能夠理解爲python解釋器裏有一個獨立的線程,每過一段時間它起wake up作一次全局輪詢看看哪些內存數據是能夠被清空的,此時你本身的程序 裏的線程和 py解釋器本身的線程是併發運行的,假設你的線程刪除了一個變量,py解釋器的垃圾回收線程在清空這個變量的過程當中的clearing時刻,可能一個其它線程正好又從新給這個還沒來及得清空的內存空間賦值了, 結果就有可能新賦值的數據被刪除了,爲了解決相似的問題,python解釋器簡單粗暴的加了鎖,即當一個線程運行時,其它人都不能動,這樣就解決了上述的問題,  這能夠說是Python早期版本的遺留問題。
相關文章
相關標籤/搜索