python併發編程——多線程

編程的樂趣在於讓程序越來越快,這裏將給大家介紹一個種加快程序運行的的編程方式——多線程

 

1 著名的全局解釋鎖(GIL)

說起python併發編程,就不得不說著名的全局解釋鎖(GIL)了。有興趣的同學可以我查找一下相關的資料瞭解一下GIL,在這裏大家只要知道一點,因爲GIL的存在, 對於任何Python程序,不管有多少的處理器,任何時候都總是隻有一個線程在執行。
下面先看一個例子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Mon Jun  5 16:12:14 2017

@author: 80002419
"""
import threading
import time

def cost(fun):##定義一個裝飾器,用來計算函數運行的時間
    def wrapper(*args,**kargs):
        before_tm = time.time()
        fun(*args,**kargs);
        after_tm = time.time()
        fun.__doc__
        print("{0} cost:{1}".format(fun.__name__,after_tm-before_tm))
    return wrapper

def fibs1(n):
    if (n == 1):
        return 0
    elif(n == 2):
        return 1
    else:
        return fibs1(n-2)+fibs1(n-1)
    
def fibs2(n):
    list = []
    if (n == 1):
        list = [0]
        return list[-1] 
    elif(n == 2):
        list = [0,1]
        return list[-1] 
    else:
        list = [0,1]
        for i in range(2,n):
            list.append(list[i-1]+list[i-2])
        return list[-1]    
 
@cost
def nothread():
    fibs1(35) 
    fibs1(35)


#@cost
#def nothread1():
#    print(fibs2(40))
#    print(fibs2(40))

@cost
#使用多線程的程序
def inthread():
    threads = []
    for i in range(2):
        t = threading.Thread(target = fibs1,args =(35,))
        t.start()
        threads.append(t)
    for t in  threads:
        t.join()

@cost    
def inthread1():
    for i in range(2):
        t = threading.Thread(target=fibs1, args=(35,))
        t.start()
    main_thread = threading.currentThread()
    for t in threading.enumerate():
        if t is main_thread:
            continue
        t.join()

nothread()
inthread()
打印結果:
nothread cost:5.41788887978
inthread cost:14.6728241444
 
上面的例子對比了一個cpu密集型程序分別使用多線程和不使用多線程的情況,我們可以得到結論: 對於cpu密集型任務,使用多線程反而會減慢程序的運行。
這是爲什麼呢?
 因爲GIL在任何時候,僅僅允許一個單一的線程能夠獲取Python對象或者C API。每100個字節的Python指令解釋器將重新獲取鎖,這(潛在的)阻塞了I/O操作
同時  GIL是必須的,這是Python設計的問題:Python解釋器是非線程安全的。這意味着當從線程內嘗試安全的訪問Python對象的時候將有一個全局的強制鎖。當然python 多線程也有可以加快程序運行的時候:當我們開發和網絡通信或者數據輸入/輸出相關的程序,比如網絡爬蟲、文本處理等等。這時候由於網絡情況和I/O的性能的限制,Python解釋器會等待讀寫數據的函數調用返回,這個時候就可以利用多線程庫提高併發效率了。這類任務我們稱爲IO(密集型任務)
 

2  Semaphore類 ——python對象訪問量的控制

在多線程編程中,爲了防止不同的線程同時對一個公用的資源(比如全部變量)進行修改,需要進行同時訪問的數量(通常是1)。信號量同步基於內部計數器,每調用一次acquire(),計數器減1;每調用一次release(),計數器加1.當計數器爲0時,acquire()調用被阻塞。
下面來看一個例子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Mon Jun  5 16:12:14 2017

@author: 80002419
"""
from threading import Thread, Semaphore 
import time,threading

sema = Semaphore(3) # 定義一個信號量爲3的Semaphone對象,

#定義一個測試函數作爲,被訪問的對象
def test(tid):
	with sema:
		print("信號量:{0}".format(tid))
		time.sleep(0.5)
		print('release!')
	
thds = []
for i in range(6):
	t = Thread(target = test,args=(i,))
	t.start()
	thds.append(t)
	
for t in thds:
	t.join()
打印結果:
信號量:2
信號量:1
信號量:0
release!
信號量:0
release!
信號量:0
release!
信號量:0
release!
release!
release!










 
結果分析 :
當信號量sema信號爲0時,此時不再不允許進程對test 再進行訪問了,release 之後才能再繼續生成親的進程對其訪問, Semaphore類的作用就是限制公共資源的訪問量
 
3 鎖——lock 與 Rlock
先看一段代碼:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Mon Jun  5 16:12:14 2017

@author: 80002419
"""
from threading import Thread, Lock
import time,threading

value = 0;

def changeValue():
	global value
	new = value + 1
	time.sleep(0.001)#讓其它進程可以切換進來
	value = new

thds = []
for _ in range(100):
	t = Thread(target = changeValue)
	t.start()
	thds.append(t)

for t in thds:
	t.join()

print(value)
打印結果:
18
結果分析: 多個線程同時訪問 value 變量,經過cpu存儲後,再寫回內存,如果進程A,進程B,在訪問value時 值爲0 ,進程A經過函數處理後,value = 1 ,再寫回內存,value =1 ,進程B 執行完成後,會刷新value 但是寫回的value 還是1, 所以並不能保護執行了100次changeValue後,結果爲100
 
那麼我們怎麼能保證執行的結果就是100呢:再看一段代碼:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Mon Jun  5 16:12:14 2017

@author: 80002419
"""
from threading import Thread, Lock
import time,threading

value = 0

lock = Lock()

def changeValue():
	global value
	with lock:
		new = value + 1
		time.sleep(0.001)#讓其它進程可以切換進來
		value = new
		
thds = []

for _ in range(100):
	t = Thread(target = changeValue)
	t.start()
	thds.append(t)
for t in thds:
	t.join()
print(value)

打印結果:
100
Lock也可以叫做互斥鎖,其實相當於信號量爲1,上面例子lock 也就可以用 sema = Semaphore()代替
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章