python異常處理,多線程,多進程


什么是異常?

異常即是一個事件,該事件會在程序執行過程中發生,影響了程序的正常執行。

一般情況下,在Python無法正常處理程序時就會發生一個異常。

異常是Python對象,表示一個錯誤。

當Python腳本發生異常時我們需要捕獲處理它,否則程序會終止執行。

異常處理

捕捉異常可以使用try / except語句。

try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常信息並處理。

如果你不想在異常發生時結束你的程序,只需在try里捕獲它。

以下為簡單的try....except...else的語法

try:
    正常的操作
   ......................
except:  #可以多次使用except捕捉多個錯誤
    發生異常,執行這塊代碼
   ......................
else:  #可有可無
    如果沒有異常執行這塊代碼

例:索引錯誤

l = [1, 2, 3, 4]
try:
    l[5]
except IndexError:
    print('索引錯誤')
else:
    print('成功')

運行結果:

l = [1, 2, 3, 4]
try:
    l[5]
except IndexError as i: #可以給錯誤個別名
    print(i)    #然后打印錯誤,會出現原生錯誤
else:
    print('成功')

運行結果:

try-finally 語句

try-finally 語句無論是否發生異常都將執行最后的代碼。

try:
<語句>
finally:
<語句>    #退出try時總會執行
raise

 例:

l = [1, 2, 3, 4]
try:
    l[5]
except IndexError:
    print('索引錯誤')
finally:
    print('happy')

運行結果:

自定義異常

通過創建一個新的異常類,程序可以命名它們自己的異常。異常應該是典型的繼承自Exception類,通過直接或間接的方式。

以下為與RuntimeError相關的實例,實例中創建了一個類,基類為RuntimeError,用於在異常觸發時輸出更多的信息。

在try語句塊中,用戶自定義的異常后執行except塊語句,變量 e 是用於創建Networkerror類的實例。

例:

class 異常錯誤名字(Exception): #自定義異常,繼承Exception類的形式

    def __init__(self, msg): #初始化
        self.message = msg

    # def __str__(self):
    #     return self.message

try:
    raise 異常錯誤名字('我的異常') #固定格式
except 異常錯誤名字 as e:
    print(e)

 

多任務多線程

線程的並發是利用cpu的上下文的切換,在python3里是假的多線程,它不是真真正正的並行,其實就是串行,只不過利用了cpu上下文的切換而已

線程是程序最小的調度單位

例:串行

def test1():
    for i in range(3):
        print('test1=======>%s' % i)
def test2():
    for i in range(3):
        print('test2=======>%s' % i)

test1()
test2()

運行結果:

例:並發

import threading #導入線程模塊
import time #導入time模塊
def test1():
    for i in range(3):
        time.sleep(1)   #為了看出效果,延遲1秒
        print('test1=======>%s' % i)
def test2():
    for i in range(3):
        time.sleep(1)   #為了看出效果,延遲1秒
        print('test2=======>%s' % i)
t1 = threading.Thread(target=test1) #實例化一個線程,用Thread方法 ,目標是test1(不加括號,是內存地址)
t2 = threading.Thread(target=test2)
t1.start()  #執行這個線程
t2.start()  #執行這個線程

運行結果:圖一出現后1秒,再次出現兩行,如圖二,再1秒后,再次出現兩行,如圖三

 

多線程執行的順序是無序的

def test1(n):
    time.sleep(1)
    print('task', n)
for i in range(10):
    t = threading.Thread(target=test1, args=('t-%s' % i,))    #傳參args,必須元組形式,必須要加逗號
    t.start()

運行結果:

多線程共享全局變量

因為函數內得不到函數外的數據,所以要global聲明

g_num = 0 #全局變量
def update():
    global g_num  #global聲明全局變量(才可以修改)
    for i in range(10):
        g_num += 1
update()
print(g_num)

運行結果:

g_num = 0 #全局變量
def update():
    global g_num  #global聲明全局變量(才可以修改)
    for i in range(10):
        g_num += 1
def reader():
    global g_num
    print(g_num)

t1 = threading.Thread(target=update)
t2 = threading.Thread(target=reader)
t1.start()
t2.start()

運行結果:

線程是繼承在進程里的,沒有進程就沒有線程

GIL全局解釋器鎖

GIL的全稱是:Global Interpreter Lock,意思就是全局解釋器鎖,這個GIL並不是python的特性,他是只在Cpython解釋器里引入的一個概念,而在其他的語言編寫的解釋器里就沒有這個GIL例如:Jython,Pypy

為什么會有GIL?

       隨着電腦多核cpu的出現核cpu頻率的提升,為了充分利用多核處理器,進行多線程的編程方式更為普及,隨之而來的困難是線程之間數據的一致性和狀態同步,而python也利用了多核,所以也逃不開這個困難,為了解決這個數據不能同步的問題,設計了gil全局解釋器鎖。

  

例:

def test1():
    global global_num #聲明全局變量
    for i in range(1000000):
        global_num += 1 #加1
    print("test1", global_num)

def test2():
    global global_num
    for i in range(1000000):
        global_num += 1
    print("test2", global_num)
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
# # t1.join()   #等t1線程執行完畢
# # t2.join()
print(global_num)

執行結果:會發現每次都是不同的

那么問題來了,為什么每次的執行結果都不一樣呢,讓我們結合上面的大圖,分析一下:

上圖和實例解析:

線程共享數據池,就相當於聲明的全局變量,線程1拿到公共數據后,首先會申請到gil鎖,就是在它進行操作時,不允許其他線程的操作,通過python解釋器,調用系統原生線程,,然后假設在cpu上執行,若執行時間到了,線程1 還沒有執行結束,那么將會被要求釋放gil鎖,相當於暫停,釋放后線程2會拿到公共數據,同樣申請到gil鎖,通過python解釋器調用原生線程,假設在cpu3上執行,並且完成+1的賦值,全局變量將會從0變成1,並且 ,線程2釋放gil鎖,線程1再次開始執行,將從暫停的位置開始,當結束+1賦值時,釋放gil鎖,這里線程1的從0到1賦值的過程將會覆蓋掉線程2的賦值,所以,全局變量還是1,接下來還是同樣的道理,不管線程1還是線程2,每一次賦值都是覆蓋形式的,所以當主線程結束的時候,每次的結果就不一樣了。

 那么上面的結果並不是我們想要的,針對上面的情況我們的解決的方案是再加一把鎖

互斥鎖,要么不作,要么做完

GIL全局解釋器鎖
import threading
global_num = 0

lock = threading.Lock() #申請一把鎖
def test1():
    global global_num
    lock.acquire() #上鎖
    for i in range(1000000):
        global_num += 1
    lock.release() #釋放鎖
    print("test1", global_num)

def test2():
    global global_num
    lock.acquire() #上鎖
    for i in range(1000000):
        global_num += 1
    lock.release() #釋放鎖
    print("test2", global_num)
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
t1.join()   #為了防止主線程結束的太快,等t1線程執行完畢
t2.join()   #為了防止主線程結束的太快,等t2線程執行完畢
print(global_num)

運行結果:

那么上過鎖之后,實際就變成了串行

例:下面代碼開了10個進程,跑了多久的時間

import time
def run(n):
    time.sleep(2)
    print('this is running======>%s' % n)

for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()

解決方案:

import time
def run(n):
    time.sleep(2)
    print('this is running======>%s' % n)
l = []  #建立一個空列表
start = time.time() #記錄開始的時間
for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()
    l.append(t) #把創建的實例加入列表
for j in l: #循環l列表
    j.join() #等待循環進程結束
end = time.time() #記錄結束的時間
print('cost', (end-start))
只要在進行耗時的IO操作的時候,能釋放GIL,所以只要在IO密集型的代碼里,用多線程就很合適

 

進程

一個程序運行起來之后,代碼+用到的資源稱之為進程,它是操作系統分配資源的基本單位,不僅可以通過線程完成多任務,進程也是可以的,cpu密集的時候適合用多進程

多進程的並發

import time import multiprocessing #導入多重處理模塊 def test1(): for i in range(10): time.sleep(1) print('test1====>', i) def test2(): for i in range(10): time.sleep(1) print('test2====>', i) if __name__=='__main__': #main的固定格式,如果沒有將報錯不支持 p1 = multiprocessing.Process(target=test1) #進程1的調用 p2 = multiprocessing.Process(target=test2) #進程2的調用  p1.start() p2.start()

一這兩個進程任務為例,如果cpu核數>=2,那么就是真真正正的 並行

運行結果:以此類推

進程之間是相互獨立的

import multiprocessing import time g_num = 0 def update(): #寫函數 global g_num for i in range(100): g_num += 1 print(g_num) def reader(): #讀函數 print(g_num) if __name__ == '__main__': p1 = multiprocessing.Process(target=update) p2 = multiprocessing.Process(target=reader) p1.start() p2.start()

運行結果:,由此看出讀函數沒有讀到寫函數更改的內容,所以得出,進程之間互相獨立,互不影響

進程池

 什么時候用進程池?

當不知道有多少個進程池要跑的時候,用進程池最合適。

import multiprocessing from multiprocessing import Pool #或者用 multiprocessing.Pool(),再給他賦變量 import time import threading g_num = 0 def test1(n): for i in range(n): time.sleep(1) print('test1', i) def test2(n): for i in range(n): time.sleep(1) print('test2', i) if __name__ == '__main__': pool = Pool() #聲明進程池 #括號內沒有聲明進程數,默認無限(根據電腦配置情況) pool.apply_async(test1, (10,)) #使用方法並且加參數 pool.apply_async(test2, (10,)) pool.close() #進程池關閉 pool.join() #進程池等待(注意的是:join必須放在close后邊)

這就是程池的並發,運行結果:

等等

協程

進程是資源分配的單位切換需要的資源最大,效率低
線程是操作系統調度的單位切換需要的資源一般,效率一般
協程是寄生在線程里的,所以協程切換任務資源很小,效率高。IO操作密集的時候首選協程
多進程、多線程根據cpu核數不一樣可能是並行的,但是協成在一個線程中

協程,其實就是串行

例:

import gevent,time def test1(): for i in range(10): time.sleep(1) #遇見延遲就切換 print('test1', i) def test2(): for i in range(10): time.sleep(1) #遇見延遲就切換 print('test2', i) g1 = gevent.spawn(test1) #聲明實例,調用函數的方法就是spawn g2 = gevent.spawn(test2) g1.join() #調用方式join,不是start g2.join()

運行結果:依次出現,由此看出是串行

協程,可以自動切換

由於協程工作時遇見延遲就會切換,所以會在test1,test2來回切換,因為test1延遲1秒,test2,延遲2秒,所以會按照2個test1,一個test2 的規律打印

import gevent,time # from gevent import monkey #打個補丁支持time.sleep() # monkey.patch_all() #讓gevent支持time.sleep() def test1(): for i in range(10): # time.sleep(1) #遇見延遲就切換 gevent.sleep(1) #延遲1秒 print('test1', i) def test2(): for i in range(10): # time.sleep(2) #遇見延遲就切換 gevent.sleep(2) #延遲2秒 print('test2', i) g1 = gevent.spawn(test1) #聲明實例,調用函數的方法就是spawn g2 = gevent.spawn(test2) g1.join() #調用方式join,不是start g2.join()

運行結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM