什麼叫「多任務」呢?簡單地說,就是操做系統能夠同時運行多個任務。打個比方,你一邊在用瀏覽器上網,一邊在聽MP3,一邊在用Word趕做業,這就是多任務,至少同時有3個任務正在運行。還有不少任務悄悄地在後臺同時運行着,只是桌面上沒有顯示而已。python
注意:web
併發:指的是任務數多餘cpu核數,經過操做系統的各類任務調度算法,實現用多個任務「一塊兒」執行(實際上總有一些任務不在執行,由於切換任務的速度至關快,看上去一塊兒執行而已)算法
並行:指的是任務數小於等於cpu核數,即任務真的是一塊兒執行的瀏覽器
1.線程網絡
1.1線程的使用方法多線程
python的thread模塊是比較底層的模塊,python的threading模塊是對thread作了一些包裝的,能夠更加方便的被使用併發
函數的多線程使用app
import timedom
import threadingasync
# 一個程序開始執行以後,會有一個主線程,當碰見xxx.start時建立子線程,主線程和子線程同時進行,
# 當主線程沒有代碼執行時,等待子線程運行完畢,主線程先結束,子線程確定結束
# 線程的運行是沒有順序的(主線程和子線程以及子線程之間),能夠添加time.sleep(xx)
def sing():
for i in range(5):
print("正在唱歌....")
time.sleep(1)
def dance():
for i in range(5):
print("正在跳舞....")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start() # start以後子線程1纔開始
# time.sleep(1)
t2.start() # start以後子線程2纔開始
if __name__ == "__main__":
main()
類的多線程使用
import threading
import time
class mythread(threading.Thread):
def run(self):
for i in range(5):
print("run方法在使用xx.start以後調用")
mythread.sing(self)
mythread.dance(self)
def sing(self):
print("正在唱歌...")
def dance(self):
print("正在跳舞...")
if __name__ == "__main__":
t = mythread()
t.start()
# mythread繼承了threading.Thread,調用start會自動調用run方法,其餘的函數能夠寫到run方法裏面
1.2多線程共享全局變量
from threading import Thread
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
print("----in work1, g_num is %d---" % g_num)
def work2():
global g_num
print("----in work2, g_num is %d---" % g_num)
print("---線程建立以前g_num is %d---" % g_num)
t1 = Thread(target=work1)
t1.start()
#延時一會,保證t1線程中的事情作完
time.sleep(1)
t2 = Thread(target=work2)
t2.start()
print("---線程建立以後g_num is %d---" % g_num)
1.3 線程-共享全局變量問題(互斥鎖)
假設兩個線程t1和t2都要對全局變量g_num(默認是0)進行加1運算,t1和t2都各對g_num加10次,g_num的最終的結果應該爲20。
可是因爲是多線程同時操做,有可能出現下面狀況:
在g_num=0時,t1取得g_num=0。此時系統把t1調度爲」sleeping」狀態,把t2轉換爲」running」狀態,t2也得到g_num=0
而後t2對獲得的值進行加1並賦給g_num,使得g_num=1
而後系統又把t2調度爲」sleeping」,把t1轉爲」running」。線程t1又把它以前獲得的0加1後賦值給g_num。
這樣致使雖然t1和t2都對g_num加1,但結果仍然是g_num=1
import threading
import time
g_num = 0
def test1(num):
global g_num
# 上鎖,此時若是以前沒有被上鎖,此時上鎖成功.
# 若是以前被上鎖那麼此時會堵塞在這裏,直到這把鎖被解開爲止
mutex.acquire()
for i in range(num):
g_num += 1
mutex.release()
print("----in work1, g_num is %s---" % str(g_num))
def test2(num):
global g_num
mutex.acquire()
for i in range(num):
g_num += 1
mutex.release()
print("----in work2, g_num is %s---" % str(g_num))
# 建立一個互斥鎖,默認是沒有上鎖的
mutex = threading.Lock()
def main():
print("---線程建立以前g_num is %s---" % str(g_num))
t1 = threading.Thread(target=test1, args=(1000000,)) # 將100增大到1000000 程序就會出錯,在執行程序時會出現搶資源問題
t2 = threading.Thread(target=test2, args=(1000000,)) # 循環次數較少時,這種狀況出現的機率比較小
t1.start()
t2.start()
time.sleep(5) # 等待兩個線程執行完畢
print("---線程建立以後g_nums is %s---" % str(g_num))
if __name__ == "__main__":
main()
上鎖解鎖過程
當一個線程調用鎖的acquire()方法得到鎖時,鎖就進入「locked」狀態。
每次只有一個線程能夠得到鎖。若是此時另外一個線程試圖得到這個鎖,該線程就會變爲「blocked」狀態,稱爲「阻塞」,直到擁有鎖的線程調用鎖的release()方法釋放鎖以後,鎖進入「unlocked」狀態。
線程調度程序從處於同步阻塞狀態的線程中選擇一個來得到鎖,並使得該線程進入運行(running)狀態。
總結
鎖的好處:
確保了某段關鍵代碼只能由一個線程從頭至尾完整地執行
鎖的壞處:
阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地降低了
因爲能夠存在多個鎖,不一樣的線程持有不一樣的鎖,並試圖獲取對方持有的鎖時,可能會形成死鎖
2.進程
程序:例如xxx.py這是程序,是一個靜態的
進程:一個程序運行起來後,代碼+用到的資源 稱之爲進程,它是操做系統分配資源的基本單元。
不只能夠經過線程完成多任務,進程也是能夠的
使用進程實現多任務,在執行程序時,會將程序複製一份給子線程.
使用線程實現多任務子線程並無把程序複製一遍進行執行
2.1 使用進程解決多任務問題
import time
import multiprocessing
def test1():
while True:
print("正在唱歌....")
time.sleep(1)
def test2():
while True:
print("正在跳舞....")
time.sleep(1)
def main():
t1 = multiprocessing.Process(target=test1)
t2 = multiprocessing.Process(target=test2)
t1.start() # start以後子線程1纔開始
# time.sleep(1)
t2.start() # start以後子線程2纔開始
if __name__ == "__main__":
main()
2.2 進程間不一樣享全局變量,使用隊列解決進程間通訊問題
import multiprocessing
def download_from_web(q):
"""下載數據"""
# 模擬從網上下載數據
data = [11, 22, 33]
# 向隊列寫入數據
for i in data:
q.put(i)
print("下載數據完畢....")
def analysis_data(q):
"""分析數據"""
store_data = list()
while True:
data = q.get()
store_data.append(data)
if q.empty(): # 判斷隊列是否爲空,爲空跳出循環
break
print(store_data)
def main():
# 建立隊列
q = multiprocessing.Queue()
# 建立多個進程,將隊列的引用當作實參傳遞到裏面
p1 = multiprocessing.Process(target=download_from_web, args=(q,))
p2 = multiprocessing.Process(target=analysis_data, args=(q,))
p1.start()
p2.start()
if __name__ == "__main__":
main()
2.3進程池pool
當須要建立的子進程數量很少時,能夠直接利用multiprocessing中的Process動態成生多個進程,但若是是上百甚至上千個目標,手動的去建立進程的工做量巨大,此時就能夠用到multiprocessing模塊提供的Pool方法。
初始化Pool時,能夠指定一個最大進程數,當有新的請求提交到Pool中時,若是池尚未滿,那麼就會建立一個新的進程用來執行該請求;但若是池中的進程數已經達到指定的最大值,那麼該請求就會等待,直到池中有進程結束,纔會用以前的進程來執行新的任務,請看下面的實例:
# -*- coding:utf-8 -*-
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print("%s開始執行,進程號爲%d" % (msg,os.getpid()))
# random.random()隨機生成0~1之間的浮點數
time.sleep(random.random()*2)
t_stop = time.time()
print(msg, "執行完畢,耗時%0.2f" % (t_stop-t_start))
po = Pool(3) # 定義一個進程池,最大進程數3
for i in range(0,10):
# Pool().apply_async(要調用的目標,(傳遞給目標的參數元祖,))
# 每次循環將會用空閒出來的子進程去調用目標
po.apply_async(worker, (i,))
print("----start----")
po.close() # 關閉進程池,關閉後po再也不接收新的請求
po.join() # 等待po中全部子進程執行完成,必須放在close語句以後
print("-----end-----")
3.協程
協程是python箇中另一種實現多任務的方式,只不過比線程更小佔用更小執行單元(理解爲須要的資源)。 爲啥說它是一個執行單元,由於它自帶CPU上下文。這樣只要在合適的時機, 咱們能夠把一個協程 切換到另外一個協程。 只要這個過程當中保存或恢復 CPU上下文那麼程序仍是能夠運行的。
通俗的理解:在一個線程中的某個函數,能夠在任何地方保存當前函數的一些臨時變量等信息,而後切換到另一個函數中執行,注意不是經過調用函數的方式作到的,而且切換的次數以及何時再切換到原來的函數都由開發者本身肯定
3.1迭代器
迭代是訪問集合元素的一種方式。迭代器是一個能夠記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。迭代器只能往前不會後退。
可迭代對象的本質就是能夠向咱們提供一個這樣的中間「人」即迭代器幫助咱們對其進行迭代遍歷使用。
可迭代對象經過__iter__方法向咱們提供一個迭代器,咱們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,而後經過這個迭代器來依次獲取對象中的每個數據.
那麼也就是說,一個具有了__iter__方法的對象,就是一個可迭代對象。
經過上面的分析,咱們已經知道,迭代器是用來幫助咱們記錄每次迭代訪問到的位置,當咱們對迭代器使用next()函數的時候,迭代器會向咱們返回它所記錄位置的下一個位置的數據。實際上,在使用next()函數的時候,調用的就是迭代器對象的__next__方法(Python3中是對象的__next__方法,Python2中是對象的next()方法)。因此,咱們要想構造一個迭代器,就要實現它的__next__方法。但這還不夠,python要求迭代器自己也是可迭代的,因此咱們還要爲迭代器實現__iter__方法,而__iter__方法要返回一個迭代器,迭代器自身正是一個迭代器,因此迭代器的__iter__方法返回自身便可。
一個實現了__iter__方法和__next__方法的對象,就是迭代器。
import time
class Classmate(object):
def __init__(self):
self.names = list()
self.current = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.current < len(self.names):
ret = self.names[self.current]
self.current += 1
return ret
else:
raise StopIteration
classmate = Classmate()
classmate.add("張三")
classmate.add("李四")
classmate.add("王五")
# print("判斷classmate是否是可迭代的對象:", isinstance(classmate, Iterable))
for name in classmate:
print(name)
time.sleep(1)
3.2 生成器
利用迭代器,咱們能夠在每次迭代獲取數據(經過next()方法)時按照特定的規律進行生成。可是咱們在實現一個迭代器時,關於當前迭代到的狀態須要咱們本身記錄,進而才能根據當前狀態生成下一個數據。爲了達到記錄當前狀態,並配合next()函數進行迭代使用,咱們能夠採用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器。
簡單來講:只要在def函數中有yield關鍵字的 就稱爲 生成器
# 生成器的做用是產生結果的方式,不能直接輸出結果
nums = [x for x in range(10)] # 列表表達式
print(nums)
num = (x for x in range(10)) # 生成器
for i in num: # 生成器產生的結果須要for循環依次取得
print(i)
print("---------分割線--------")
def Fibonacii(all_nums):
a = 0
b = 1
current_num = 0
while current_num < all_nums:
# print(a)
yield a
a, b = b, a+b
current_num += 1
print("----------獲取生成器結果的方式--------")
f = Fibonacii(10)
for i in f: # 產生生成器的結果
print(i)
print("----------獲取生成器結果的方式--------")
f1 = Fibonacii(10)
print(next(f1))
print(next(f1))
print(next(f1))
print(next(f1))
print(f1.send(None)) # send也能夠獲取生成器的值,還能夠傳參,send不能做爲第一個獲取方式
print(next(f1))
print(f1.send("傳參"))
print(next(f1))
3.3 yield -協程
import time
def task1():
while True: # 若是這裏不使用while true程序只執行一遍
print("-----task1------")
yield # 遇到yield以後暫停去執行其餘的
time.sleep(0.5)
def task2():
while True:
print("-----task2------")
yield
time.sleep(0.5)
def main():
t1 = task1()
t2 = task2()
while True:
next(t1) # 執行t1時遇到yield暫停開始執行t2,在t2執行時遇到yield而後執行t1,循環執行
next(t2)
if __name__ == "__main__":
main()
3.4 greenlet-協程
使用以下命令安裝greenlet模塊:
sudo pip3 install greenlet
from greenlet import greenlet
import time
def task1():
while True: # 若是這裏不使用while true程序只執行一遍
print("-----task1------")
gr2.switch()
time.sleep(0.5)
def task2():
while True:
print("-----task2------")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(task1)
gr2 = greenlet(task2)
gr1.switch()
3.5 gevent-協程(採用此方法實現協程)
import gevent
import time
# 協程實際上是單線程交替進行,在這裏gevent實現多任務的方法是:在程序中遇到耗時比較大的時候,或者程序堵塞的時候,
# 將目前執行程序切換成其餘程序執行,這樣來回交替,主要是將耗時的操做轉成其餘操做
def f(n): 無錫看婦科醫院哪家好 http://mobile.wxbhnkyy39.com/
for i in range(n):
print(gevent.getcurrent(), i)
# time.sleep(1) # 不能實現多任務,須要使用gevent.sleep
# 用來模擬一個耗時操做,注意不是time模塊中的sleep
gevent.sleep(1)
g1 = gevent.spawn(f, 5) # 建立一個實例,並不執行
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join() # 等待g1執行完畢,這時堵塞,切換到g2去執行
g2.join()
g3.join()
通常採用下面的寫法(採用此方法實現協程):
import gevent
from gevent import monkey
import time
# 協程實際上是單線程交替進行,在這裏gevent實現多任務的方法是:在程序中遇到耗時比較大的時候,或者程序堵塞的時候,
# 將目前執行程序切換成其餘程序執行,這樣來回交替,主要是將耗時的操做轉成其餘操做
# 這條語句在執行程序時會將程序中的耗時操做轉化爲gevent.sleep,也就是說在程序中可使用time.sleep,也能夠出現
# 堵塞的狀況,好比udp或tcp出現的網絡堵塞
monkey.patch_all()
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(1) # 不能實現多任務,須要使用gevent.sleep
# 用來模擬一個耗時操做,注意不是time模塊中的sleep
# gevent.sleep(1)
# g1 = gevent.spawn(f, 5) # 建立一個實例,並不執行
# g2 = gevent.spawn(f, 5)
# g3 = gevent.spawn(f, 5)
#
# g1.join() # 等待g1執行完畢,這時堵塞,切換到g2去執行
# g2.join()
# g3.join()
# 下面這條語句使用替換了上述調用的方法
gevent.joinall([
gevent.spawn(f, 5),
gevent.spawn(f, 5)
])
4.線程 進程 協程的對比
請仔細理解以下的通俗描述
有一個老闆想要開個工廠進行生產某件商品(例如剪子)
他須要花一些財力物力製做一條生產線,這個生產線上有不少的器件以及材料這些全部的 爲了可以生產剪子而準備的資源稱之爲:進程
只有生產線是不可以進行生產的,因此老闆的找個工人來進行生產,這個工人可以利用這些材料最終一步步的將剪子作出來,這個來作事情的工人稱之爲:線程
這個老闆爲了提升生產率,想到3種辦法:
在這條生產線上多招些工人,一塊兒來作剪子,這樣效率是成倍増長,即單進程 多線程方式
老闆發現這條生產線上的工人不是越多越好,由於一條生產線的資源以及材料畢竟有限,因此老闆又花了些財力物力購置了另一條生產線,而後再招些工人這樣效率又再一步提升了,即多進程 多線程方式
老闆發現,如今已經有了不少條生產線,而且每條生產線上已經有不少工人了(即程序是多進程的,每一個進程中又有多個線程),爲了再次提升效率,老闆想了個損招,規定:若是某個員工在上班時臨時沒事或者再等待某些條件(好比等待另外一個工人生產完謀道工序 以後他才能再次工做) ,那麼這個員工就利用這個時間去作其它的事情,那麼也就是說:若是一個線程等待某些條件,能夠充分利用這個時間去作其它事情,其實這就是:協程方式
簡單總結
進程是資源分配的單位
線程是操做系統調度的單位
進程切換須要的資源很最大,效率很低
線程切換須要的資源通常,效率通常(固然了在不考慮GIL的狀況下)
協程切換任務資源很小,效率高
多進程、多線程根據cpu核數不同多是並行的,可是協程是在一個線程中 因此是併發
按效率來分,協程>線程>進程