多線程多進(jìn)程理解

概念

一、多任務(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容