概念
一、多任務(wù)
就是多個(gè)任務(wù)同時(shí)執(zhí)行
程序中:任務(wù)就可以指的是函數(shù),一個(gè)函數(shù)就是一個(gè)任務(wù),如果想多個(gè)任務(wù)同時(shí)執(zhí)行,就要使用多進(jìn)程、多線程來實(shí)現(xiàn)
同步、異步、并發(fā)、并行
同步:
執(zhí)行完任務(wù)A,才能執(zhí)行B任務(wù)
異步:
執(zhí)行任務(wù)A的同時(shí)可以執(zhí)行任務(wù)B
并發(fā):
偽異步,任務(wù)a和任務(wù)b在同一時(shí)刻不是同時(shí)運(yùn)行,但是在一段時(shí)間范圍內(nèi),都在運(yùn)行(交替執(zhí)行任務(wù))
通過時(shí)間切片,CUP不同的切換,由于速度快,所以就會(huì)像是在同時(shí)執(zhí)行,(這里還有一個(gè)上下文概念和寄存器在這里不做過多解釋)并行:
真異步,任務(wù)a和任務(wù)b真的在同時(shí)運(yùn)行()
關(guān)于并發(fā)和并行的比較
區(qū)別:是否是同時(shí)處理任務(wù)
并發(fā):交替執(zhí)行任務(wù)
并發(fā)的關(guān)鍵是你有處理多個(gè)任務(wù)的能力,不一定要同時(shí)。
并行:同時(shí)執(zhí)行任務(wù)
并行的關(guān)鍵是你有同時(shí)處理多個(gè)任務(wù)的能力,強(qiáng)調(diào)的是同時(shí)
并發(fā)表示同時(shí)發(fā)生了多件事情,通過時(shí)間片切換,哪怕只有單一的核心,也可以實(shí)現(xiàn)“同時(shí)做多件事情”這個(gè)效果
并發(fā)是獨(dú)立執(zhí)行過程的組合,而并行是同時(shí)執(zhí)行計(jì)算
并發(fā)是一次處理很多事情,并且是同時(shí)做很多事情
并行才是我們通常認(rèn)為的那個(gè)同時(shí)做很多事情,而并行則是在線程這個(gè)模型下產(chǎn)生的概念。并發(fā)表示同時(shí)發(fā)生了很多事情,通過時(shí)間切片,哪怕只有單一的核心,也可以實(shí)現(xiàn)“同時(shí)做多個(gè)事情”這個(gè)效果。根據(jù)底層是否有多個(gè)處理器,兵法與并行是可以等效的,這并不是連個(gè)互斥的概念。
舉個(gè)例子:
開發(fā)中說資源請(qǐng)求并發(fā)數(shù)達(dá)到了1萬。這里的意思是:有一萬個(gè)請(qǐng)求同時(shí)過來了。但是這里很明顯不可能真正的同時(shí)處理1萬個(gè)請(qǐng)求。如果這臺(tái)計(jì)算機(jī)的處理器有4 個(gè)核,不考慮超線程,那么我們認(rèn)為同時(shí)會(huì)有4個(gè)線程在跑。也就是說:并發(fā)訪問數(shù)是一萬,而底層真實(shí)的并行處理的請(qǐng)求數(shù)是4 如果并發(fā)數(shù)小一些只有4的話,又或者你的機(jī)器牛逼有1萬個(gè)核心,那并發(fā)在這里和并行一個(gè)效果。也就是說,并發(fā)可以是虛擬的同時(shí)執(zhí)行,也可以是真的同時(shí)執(zhí)行。而并行的意思是真的同時(shí)執(zhí)行
結(jié)論是:并行是我們屋里時(shí)空觀下的同時(shí)執(zhí)行,而并發(fā)則是操作系統(tǒng)用線程這個(gè)模型抽象之后站在線程的視角看到的“同時(shí)”執(zhí)行
一個(gè)多核cpu在處理多個(gè)任務(wù)的時(shí)候如果想要發(fā)揮最大功效就要實(shí)現(xiàn)并行
多進(jìn)程可以充分使用cpu的兩個(gè)內(nèi)核,而多線程卻不能充分使用cpu的兩個(gè)內(nèi)核,多線程并不能真正的讓多核cpu實(shí)現(xiàn)并行
原因:
cpython解釋器中存在一個(gè)GIL(全局線程解釋器鎖),它的作用就是保證同一時(shí)刻只有一個(gè)線程可以執(zhí)行代碼,因此造成了使用多線程的時(shí)候無法實(shí)現(xiàn)并行。
什么是GIL
Python語言和GIL沒有半毛錢關(guān)系。僅僅是由于歷史原因在Cpython虛擬機(jī)(解釋器),難以移除GIL。
GIL:全局解釋器鎖。每個(gè)線程在執(zhí)行的過程都需要先獲取GIL,保證同一時(shí)刻只有一個(gè)線程可以執(zhí)行代碼。
線程釋放GIL鎖的情況: 在IO操作等可能會(huì)引起阻塞的system call之前,可以暫時(shí)釋放GIL,但在執(zhí)行完畢后,必須重新獲取GIL Python 3.x使用計(jì)時(shí)器(執(zhí)行時(shí)間達(dá)到閾值后,當(dāng)前線程釋放GIL)或Python 2.x,tickets計(jì)數(shù)達(dá)到100
Python使用多進(jìn)程是可以利用多核的CPU資源的。
多線程爬取比單線程性能有提升,因?yàn)橛龅絀O阻塞會(huì)自動(dòng)釋放GIL鎖
結(jié)論:
1. 在 處理像科學(xué)計(jì)算 這類需要持續(xù)使用cpu的任務(wù)的時(shí)候 單線程會(huì)比多線程快
2. 在 處理像IO操作等可能引起阻塞的這類任務(wù)的時(shí)候 多線程會(huì)比單線程
二、進(jìn)程
進(jìn)程如何理解
一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程
進(jìn)程在執(zhí)行的過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率
進(jìn)程:是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位
電腦中:一個(gè)軟件,就是一個(gè)進(jìn)程,windows里面有進(jìn)程管理器,linux里面通過 kill -9 進(jìn)程id號(hào)
代碼中:你寫了一個(gè)py代碼,代碼在沒有運(yùn)行之間稱之為程序,代碼運(yùn)行起來就是一個(gè)進(jìn)程
如果只有一個(gè)進(jìn)程,我們稱之為主進(jìn)程,如果還有其它進(jìn)程,肯定是通過主進(jìn)程創(chuàng)建的子進(jìn)程
進(jìn)程之間是否共享局部變量
不共享
進(jìn)程之間是否共享全局變量
不共享
(1)進(jìn)程創(chuàng)建方式
面向過程創(chuàng)建
p = Process(target=xxx, args=(xxx,))
target: 進(jìn)程啟動(dòng)要執(zhí)行的方法
args: 主進(jìn)程給子進(jìn)程要傳遞的參數(shù)
p.start(): 啟動(dòng)進(jìn)程
p.join(): 讓主進(jìn)程等待
from multiprocessing import Process
import time
import os
def sing(a, b):
print('%s比%s漂亮嗎' % (b, a))
print('sing進(jìn)程的id號(hào)為--%s--' % os.getpid())
print('sing進(jìn)程的父進(jìn)程id號(hào)為--%s--' % os.getppid())
for x in range(1, 6):
print('我在唱女兒情')
time.sleep(1)
def dance():
print('dance進(jìn)程的id號(hào)為--%s--' % os.getpid())
print('dance進(jìn)程的父進(jìn)程id號(hào)為--%s--' % os.getppid())
for x in range(1, 6):
print('我在跳廣場(chǎng)舞')
time.sleep(1)
def main():
print('主進(jìn)程的id號(hào)為--%s--' % os.getpid())
name1 = '大道東'
name2 = '哈哈哈'
# 主進(jìn)程在這里,創(chuàng)建兩個(gè)子進(jìn)程
p_sing = Process(target=sing, args=(name1, name2))
p_dance = Process(target=dance)
# 啟動(dòng)進(jìn)程
p_sing.start()
p_dance.start()
# 讓主進(jìn)程等待子進(jìn)程結(jié)束之后再結(jié)束
p_sing.join()
p_dance.join()
print('主進(jìn)程運(yùn)行結(jié)束')
if __name__ == '__main__':
main()
2)面向?qū)ο髣?chuàng)建
class MyProcess(Process):
def __init__(self, name):
super().__init__()
def run():
pass
from multiprocessing import Process
import time
class SingProcess(Process):
def __init__(self, name):
# 手動(dòng)調(diào)用父類的構(gòu)造方法
super().__init__()
self.name = name
# 重寫父類的方法,啟動(dòng)之后直接調(diào)用
def run(self):
print('主進(jìn)程傳遞過來的參數(shù)為%s' % self.name)
for x in range(1, 6):
print('我在唱小幸運(yùn)')
time.sleep(1)
class DanceProcess(Process):
def run(self):
for x in range(1, 6):
print('我在跳拉丁舞')
time.sleep(1)
def main():
name = '柳巖'
p_sing = SingProcess(name)
p_dance = DanceProcess()
p_sing.start()
p_dance.start()
p_sing.join()
p_dance.join()
print('主進(jìn)程-子進(jìn)程全部結(jié)束')
if __name__ == '__main__':
main()
進(jìn)程池
進(jìn)程是不是越多越好呢?
創(chuàng)建進(jìn)程肯定需要空間的。切換也需要時(shí)間和資源
格式
from multiprocessing import Pool
p = Pool(3)
p.apply_async(xxx, args=(xxx,)) 添加任務(wù)
p.close() 添加任務(wù)完畢,關(guān)閉池子
p.join() 讓主進(jìn)程等待
進(jìn)程池
from multiprocessing import Pool
import os
import time
import random
def demo(name):
print('%s--任務(wù)正在執(zhí)行,進(jìn)程id號(hào)為--%s--' % (name, os.getpid()))
lt = [2, 3, 4]
time.sleep(random.choice(lt))
def main():
# 創(chuàng)建進(jìn)程池 代表的意思就是最多創(chuàng)建3個(gè)進(jìn)程
pol = Pool(3)
# 給池子添加任務(wù)
names_list = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠', '張遼', '典韋', '許褚', '夏侯惇', '于禁']
for name in names_list:
pol.apply_async(demo, args=(name,))
# 關(guān)閉池子,不允許添加任務(wù)了
pol.close()
# 讓主進(jìn)程等待
pol.join()
print('主進(jìn)程-子進(jìn)程全部結(jié)束')
if __name__ == '__main__':
main()
線程
是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,比進(jìn)程更小的能獨(dú)立的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源,但是他可以與同屬一個(gè)進(jìn)程的其他線程共享進(jìn)程所有資源
進(jìn)程與線程的關(guān)系
線程屬于進(jìn)程,一個(gè)線程只能屬于一個(gè)進(jìn)程,一個(gè)進(jìn)程可以擁有多個(gè)線程
系統(tǒng)分配資源的時(shí)候,以進(jìn)程為單位分配
系統(tǒng)執(zhí)行的時(shí)候,以線程為單位執(zhí)行
一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程,
線程不能夠獨(dú)立執(zhí)行,必須依存在進(jìn)程中
線程的資源比進(jìn)程少,使得多線程程序的并發(fā)性高
進(jìn)程在執(zhí)行的過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率
多線程在解決高并發(fā)問題中所起到的作用就是:
使計(jì)算機(jī)的資源在每一時(shí)刻都能達(dá)到最大的利用率,不至于浪費(fèi)計(jì)算機(jī)資源使其閑置
優(yōu)缺點(diǎn)
線程執(zhí)行開銷小,但不利于資源管理和保護(hù),不穩(wěn)定(一個(gè)線程子線程掛了,影響整個(gè)主線程)
進(jìn)程執(zhí)行開銷大,好管理,穩(wěn)定(一個(gè)子進(jìn)程掛了不影響整個(gè)進(jìn)程)
若調(diào)用run,相當(dāng)于正常的函數(shù)調(diào)用,將按照程序的順序執(zhí)行
若調(diào)用start,則先執(zhí)行主進(jìn)程,后執(zhí)行子進(jìn)程
若調(diào)用run,相當(dāng)于正常的函數(shù)調(diào)用,將按照程序的順序執(zhí)行
面向過程創(chuàng)建線程
import threading
import time
def sing(a):
print('sing線程的名字為%s' % threading.current_thread().name)
print('傳遞過來的參數(shù)為%s' % a)
for x in range(1, 6):
print('我在唱女兒情')
time.sleep(1)
def dance():
print('dance線程的名字為%s' % threading.current_thread().name)
for x in range(1, 6):
print('我在跳廣場(chǎng)舞')
time.sleep(1)
def main():
name = '馬爾扎哈'
print('主線程的名字為%s' % threading.current_thread().name)
# 創(chuàng)建線程
t_sing = threading.Thread(target=sing, name='唱歌', args=(name,))
t_dance = threading.Thread(target=dance)
# 啟動(dòng)線程
t_sing.start()
t_dance.start()
# 讓主線程等待
t_sing.join()
t_dance.join()
print('主線程-子線程全部運(yùn)行結(jié)束')
if __name__ == '__main__':
main()
多線程執(zhí)行同一個(gè)任務(wù)
經(jīng)測(cè)試比正常的 快一倍
def func():
a = requests.get(url).text
if __name__ == '__main__':
task = []
for i in range(400):
t = threading.thread(targer=func)
t.start()
task.append(t)
for i in task:
i.join()
面向?qū)ο髣?chuàng)建線程
import threading
import time
class SingThread(threading.Thread):
def __init__(self, a):
super().__init__()
self.a = a
# 就是給當(dāng)前線程起名字的
self.name = '高爾夫'
def run(self):
print('傳遞過來的參數(shù)為%s' % self.a)
print('當(dāng)前線程的名字為%s' % threading.current_thread().name)
for x in range(1, 6):
print('我在唱女兒情')
time.sleep(1)
class DanceThread(threading.Thread):
def run(self):
for x in range(1, 6):
print('我在跳廣場(chǎng)舞')
time.sleep(1)
def main():
a = '關(guān)之琳'
# 創(chuàng)建線程
t_sing = SingThread(a)
t_dance = DanceThread()
# 啟動(dòng)線程
t_sing.start()
t_dance.start()
# 讓主線程等待
t_sing.join()
t_dance.join()
print('主線程-子線程全部運(yùn)行結(jié)束')
if __name__ == '__main__':
main()
注:
線程之間是否共享局部變量
不共享
線程之間是否共享全局變量
共享
四、隊(duì)列
隊(duì)列:排隊(duì)買飯,排隊(duì)買票
特點(diǎn):先進(jìn)先出
棧:先進(jìn)后出,子彈,函數(shù)的調(diào)用過程
格式
from queue import Queue
q = Queue(5)
q.put(xxx)
q.put(xxx, False) 隊(duì)列滿,立即拋出異常
q.put(xxx, True, 5) 隊(duì)列滿,5s之后拋出異常
q.get()
q.get(False) 隊(duì)列空,立即拋出異常
q.get(True, 5) 隊(duì)列空,5s之后拋出異常
q.full() 是否滿
q.empty() 是否空
q.qsize() 獲取隊(duì)列元素的個(gè)數(shù)
from queue import Queue
# 創(chuàng)建一個(gè)隊(duì)列
# 5代表里面只能放5個(gè)元素,可以不寫,不寫代表無限長
q = Queue(5)
print(q.full())
print(q.empty())
print(q.qsize())
# 向隊(duì)列中添加元素
q.put('林霞')
q.put('宋英')
q.put('王賢')
print(q.full())
print(q.empty())
print(q.qsize())
q.put('智')
q.put('林嬌')
# q.put('章澤天', True, 5)
# 從隊(duì)列中獲取元素
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# print(q.get(True, 5))
五、鎖
為什么會(huì)有鎖的概念?
原因:進(jìn)程之間不管是全局變量還是局部變量不會(huì)共享,但是線程之間共享全局變量,一個(gè)線程在利用資源的時(shí)候,其他線程就不能用,所以要保證同一時(shí)刻只有一個(gè)線程在執(zhí)行任務(wù)
加鎖、釋放鎖,同一把鎖
from threading import Lock
lock = Lock() 創(chuàng)建一把鎖
lock.acquire() 上鎖
lock.release() 釋放鎖
多線程實(shí)現(xiàn)同步的四種方式
https://blog.csdn.net/sunhuaqiang1/article/details/69389316?utm_source=blogxgwz2
信號(hào)量
https://blog.csdn.net/a349458532/article/details/51589460?utm_source=blogxgwz0
0.39.png