python多线程 不在main_Python多线程
一、線程概念
在程序運行時,操作系統(tǒng)會創(chuàng)建一個進程,并且會創(chuàng)建一個線程,這個線程就是主線程,主線程可以創(chuàng)建子線程。線程看上去同時運行,其實是按照并發(fā)執(zhí)行的,走走停停,一直到所有形線程完成為止。線程像進程一樣會有生命周期,如下所示:
將程序進行多線程編程,其性能會得到很大提升。python線程對CPU密集型性能提高不大,對I/O密集型性能提高很大。
二、多線程示例
import threading
import time
class myThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
out() #加入執(zhí)行的程序代碼
def out():
print_time1()
print_time2()
def print_time1():
print("Locka is acquired")
print("Lockb is acwuired")
def print_time2():
print("Lockb is acquired")
time.sleep(2)
print("Locka is acwuired")
def main():
for i in range(50):
t = myThread()
t.start() #執(zhí)行線程
main()
以上就是線程的簡單程序,我們創(chuàng)建了50個線程,將他們同時運行。他們完成的時間不一樣,其先后順序也不能確定。使用方法就是自己寫一個類,繼承threading.Thread類,并重寫方法run(),將自己要運行的程序放入run()函數(shù)之中就行。
但是,上述程序有一個問題,就是在調(diào)用函數(shù)out()時,可能在一個線程還沒有執(zhí)行完時,就暫停,CPU轉(zhuǎn)而去執(zhí)行另一個線程,導(dǎo)致另一個線程修改了這個線程的數(shù)據(jù),導(dǎo)致輸出錯誤的結(jié)果。解決辦法就是在同一時刻就只能有一個線程訪問臨界資源,其他線程只能等待。
三、線程同步
在python中實現(xiàn)線程同步有多種方法
1. 線程鎖(Lock)
GIL(全局解釋器鎖)
GIL并不是Python的特性,它是在實現(xiàn)Python解析器(CPython)時所引入的一個概念,是為了實現(xiàn)不同線程對共享資源訪問的互斥,才引入了GIL。以下是原理圖:
我們對臨界資源加上鎖,這樣其他線程就無法訪問,直到這個線程完成操作,釋放線程鎖之后為止。如下代碼:
import threading
import time
#創(chuàng)建鎖
locka = threading.Lock()
lockb = threading.Lock()
class myThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
out()
def out():
print_time1()
print_time2()
def print_time1():
locka.acquire() #獲取鎖
print("Locka is acquired")
lockb.acquire() #獲取鎖
print("Lockb is acwuired")
lockb.release() #釋放鎖
locka.release() #釋放鎖
def print_time2():
lockb.acquire()
print("Lockb is acquired")
time.sleep(2)
locka.acquire()
print("Locka is acwuired")
locka.release()
lockb.release()
def main():
for i in range(50):
t = myThread()
t.start()
main()
在上面程序中,我們創(chuàng)建了兩個鎖locka和lockb,分別對臨界資源加鎖,這樣就可以讓同一時刻就只有一個線程執(zhí)行,避免輸出錯誤結(jié)果。但是上述代碼還有一個錯誤,當(dāng)?shù)谝粋€線程執(zhí)行到函數(shù)print_time2()的time_sleep(2)時,需要獲取鎖locka,但是locka已經(jīng)被第二個線程獲取,還沒有釋放,而且第二個線程也需要獲取lockb才能繼續(xù)運行,但是lockb已被第一個線程獲取,還沒有釋放,就這樣,兩個線程會一直等待,陷入死鎖。解決辦法是引入可重入鎖。
2.遞歸鎖(RLock)
遞歸鎖就是在同一個線程中可以獲取鎖所多次,不會陷入死鎖。但是在acquire()n次之后,需要release()n次。
import threading
import time
rlock = threading.RLock() #RLock本身有一個計數(shù)器,如果碰到acquire,那么計數(shù)器+1
#如果計數(shù)器大于0,那么其他線程無法查收,如果碰到release,計數(shù)器-1
class myThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
out()
def out():
print_time1()
print_time2()
def print_time1():
rlock.acquire() #獲取鎖
print("Locka is acquired")
rlock.acquire() #獲取鎖
print("Lockb is acwuired")
rlock.release() #釋放鎖
rlock.release() #釋放鎖
def print_time2():
rlock.acquire()
print("Lockb is acquired")
time.sleep(2)
rlock.acquire()
print("Locka is acwuired")
rlock.release()
rlock.release()
def main():
for i in range(50):
t = myThread()
t.start()
main()
三、Semaphore(信號量)
threading模塊里的Semaphore類實現(xiàn)了信號量對象,可用于控制獲取資源的線程數(shù)量。所具有的acquire()和release()方法,可以用with語句的上下文管理器。當(dāng)進入時,將調(diào)用acquire()方法,當(dāng)退出時,將調(diào)用release()。
import threading
import time
sem = threading.Semaphore(3) #設(shè)置線程并發(fā)數(shù)
class myThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
out()
def out():
print_time1()
print_time2()
def print_time1():
sem.acquire() #線程數(shù)減一
print("Locka is acquired")
print("Lockb is acwuired")
sem.release() #線程數(shù)加一
def print_time2():
sem.acquire() #線程數(shù)減一
print("Lockb is acquired")
print("Locka is acwuired")
sem.release() #線程數(shù)加一
def main():
for i in range(10):
t = myThread()
t.start()
main()
四、Condition(條件變量)
Condition(條件變量)通常與一個鎖關(guān)聯(lián)。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構(gòu)造方法,否則它將默認生成一個RLock實例。
可以認為,除了Lock帶有的鎖定池外,Condition還包含一個等待池,池中的線程處于狀態(tài)圖中的等待阻塞狀態(tài),直到另一個線程調(diào)用notify()/notifyAll()通知;得到通知后線程進入鎖定池等待鎖定。
Condition():
acquire(): 線程鎖
release(): 釋放鎖
wait(timeout): 線程掛起,并釋放鎖,直到收到一個notify通知或者超時(可選的,浮點數(shù),單位是秒s)才會被喚醒繼續(xù)運行。wait()必須在已獲得Lock前提下才能調(diào)用,否則會觸發(fā)RuntimeError。
notify(n=1): 調(diào)用這個方法將從等待池挑選一個線程并通知,收到通知的線程將自動調(diào)用acquire()嘗試獲得鎖定(進入鎖定池);其他線程仍然在等待池中。調(diào)用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。 最多喚醒n個等待的線程。
notifyAll(): 調(diào)用這個方法將通知等待池中所有的線程,這些線程都將進入鎖定池嘗試獲得鎖定。調(diào)用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。
以下就以生產(chǎn)者消費者為例:
import threading
import time
import random
con = threading.Condition()
class Goods():
def __init__(self):
self.__goods = 0
def getgoods(self):
return self.__goods
def add(self):
self.__goods += 1
def sub(self):
self.__goods -= 1
def isEmpty(self):
if self.__goods <= 0:
return True
else:
return False
def isFull(self):
if self.__goods >= 10:
return True
else:
return False
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
con.acquire()#獲取鎖
while goods.isFull(): #貨物滿了,需要消費才能生產(chǎn),進入阻塞
con.wait()
goods.add()#生產(chǎn)一件貨物
print("生產(chǎn)一件貨物,總貨物數(shù)量為:", goods.getgoods())
con.notifyAll()#生產(chǎn)一件貨物后便喚醒所有正在等待的消費者
con.release()#釋放鎖
time.sleep(random.random())
class Consumer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
con.acquire()#獲取鎖
while goods.isEmpty():#貨物消費完了,需要生產(chǎn)貨物,進入阻塞
con.wait()
goods.sub()#消費一件貨物
print("消費一件貨物,總貨物數(shù)量為:", goods.getgoods())
con.notifyAll()#消費一件貨物后便喚醒所有正在等待的生產(chǎn)者
con.release()#釋放鎖
time.sleep(random.random())
goods = Goods()
def main():
threads = []
#threads.append(Producer())
#threads.append(Comsumer())
for i in range(5):
threads.append(Producer())
for i in range(5):
threads.append(Consumer())
for th in threads:
th.start()
main()
五、同步隊列
讓我們考慮更復(fù)雜的一種場景:產(chǎn)品是各不相同的。這時只記錄一個數(shù)量就不夠了,還需要記錄每個產(chǎn)品的細節(jié)。很容易想到需要用一個容器將這些產(chǎn)品記錄下來。
Python的Queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(后入先出)隊列LifoQueue,和優(yōu)先級隊列PriorityQueue。這些隊列都實現(xiàn)了鎖原語,能夠在多線程中直接使用。可以使用隊列來實現(xiàn)線程間的同步。
用FIFO隊列實現(xiàn)上述生產(chǎn)者與消費者問題的代碼如下:
import threading
import time
import random
import queue
q = queue.Queue() #創(chuàng)建一個線程同步隊列
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
item = random.randint(0, 16)
while q.qsize() >= 10:
pass
q.put(item) #添加貨物
print("生產(chǎn)貨物%02d, 隊列大小:%02d" % (item, q.qsize()))
time.sleep(random.randint(0, 3))
class Consumer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
item = q.get() #消費貨物,若為空,會阻塞
print("消費貨物%02d, 隊列大小:%02d" % (item, q.qsize()))
time.sleep(random.randint(0, 3))
def main():
threads = []
for i in range(5):
threads.append(Producer())
for i in range(5):
threads.append(Consumer())
for th in threads:
th.start()
main()
六、Event(事件)
python線程的事件用于主線程控制其他線程的執(zhí)行,事件主要提供了三個方法wait、clear、set。
事件處理的機制:全局定義了一個“Flag”,如果“Flag”值為 False,那么當(dāng)程序執(zhí)行 event.wait 方法時就會阻塞,如果“Flag”值為True,那么event.wait 方法時便不再阻塞。
event.set() 設(shè)置標志位為True
event.clear() 清空標志位,標志位為False
event.wait() 等待設(shè)置標志位,阻塞
event.isSet() 判斷標志位是True還是False
下面就采用紅綠燈車通行的例子來示例:
import threading
import time
event = threading.Event()
def Lighter():
event.set()
count = 0
while True:
if count > 5 and count <= 10: #紅燈
event.clear() #清除標志位
elif count > 10: #變?yōu)榫G燈
event.set() #重新設(shè)置標志位
count = 0
time.sleep(1)
count += 1
def Car(name):
while True:
if event.isSet(): #判斷標志位為True
print("light is green, %d is running" % name)
time.sleep(2)
else:
print("light is red, %d is waiting" % name)
event.wait() #阻塞,停車
def main():
light = threading.Thread(target=Lighter)
light.start()
threads = []
for i in range(5): #開五部車
threads.append(threading.Thread(target=Car, args=(i,)))
for car in threads:
car.start()
main()
這個程序?qū)⒓t綠燈的情況來控制車的通行,即用紅綠燈這線程來控制車線程,達到一個線程控制多個線程的目的。
總結(jié)
以上是生活随笔為你收集整理的python多线程 不在main_Python多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 耳机是怎么传输声音的_win7电脑耳机有
- 下一篇: log python_基于Python