併發編程

進程

  • 1、什麼是進程
    進程指的就是一個正在運行的程序,或者說是程序的運行過程,即進程是一個抽象的概念
    進程是起源於操作系統的,是操作系統最核心的概念,操作系統所有其他的概念都是圍繞進程展開的
    其中就有了多道技術的來由
    用進程就是爲了實現併發

  • 操作系統(現代操作系統):
    操作系統是位於計算機硬件於軟件之間的控制程序
    作用:
    1、將硬件的複雜操作封裝成簡單的接口,給用戶或者應用程序使用
    2、將多個應用程序對硬件的競爭變的有序

  • 進程
    一個正在運行的程序,或者說是一個程序的運行過程

  • 串行、併發、並行
    串行:一個任務完完整運行完畢,才執行下一個
    併發:多個任務看起來是同時運行的,單核就可以實現併發
    並行:多個任務是真正意義上的同時運行,只有多核才能實現並行

  • 多道技術
    背景:想要再單核下實現併發(單核同一時刻只能執行一個任務(每起一個進程就會產生一把GIL全局解釋器鎖))
    併發實現的本質就:切換+保存狀態
    多道技術:
    1、空間上的複用=》多個任務共用一個內存條,但佔用內存是彼此隔離的,而且是物理層面隔離的
    2、時間上的複用=》多個任務共用同一個cpu
    切換:
    1、遇到io切換
    2、一個任務佔用cpu時間過長,或者有另外一個優先級更高的任務搶走的cpu



開啓進程的兩種方式


 image.png



image.png



image.png




  • 進程的方法與屬性:

  • join:
    讓父進程在原地等待,等到子進程運行完畢後(會觸發wait功能,將子進程回收掉),才執行下一行代碼

  • terminate:
    終止進程,應用程序給操作系統發送信號,讓操作系統把這個子程序幹掉 ,至於多久能幹死,在於操作系統什麼時候執行這個指令

  • is_alive: 
    查看子進程是否存在,存在返回True,否則返回False

  • os.getpid: 
    導入os模塊,查看自己的門牌號

  • os.getppid: 
    導入os模塊,查看父的門牌號

  • current_process().name: 
    導入from multiprocessing import Process,current_process
    查看子進程的名字
    主進程等子進程是因爲主進程要給子進程收屍
    進程必須等待其內部所有線程都運行完畢才結束

    孤兒進程:

    在父進程被幹掉的情況下會變成孤兒進程,無害,會被孤兒院((linux的孤兒院)init)回收

    殭屍進程:父進程沒死,子進程死了,這時候的子進程就是殭屍進程

    正常情況下無害(會調用wait()方法進行回收操作), 父進程無限循環,且不被回收的情況下會無限制的生成子進程從而佔用大量的操作系統資源
    當操作系統被大量殭屍進程佔滿內存後,操作系統就無法在啓動其他的程序



image.png




1、守護進程
守護進程其實就是一個“子進程”
守護進程會伴隨主進程的代碼運行完畢後而死掉
進程:
當父進程需要將一個任務並發出去執行,需要將該任務放到一個子進程裏
守護:
當該子進程內的代碼在父進程代碼運行完畢後就沒有存在的意義了,就應該
將該子進程設置爲守護進程,會在父進程代碼結束後死掉
實例:
from multiprocessing import Process
import time,os

def task(name):
        print('%s is running' %name)
        time.sleep(3)
        if __name__ == '__main__':
        p1=Process(target=task,args=('守護進程',))
        p2=Process(target=task,args=('正常的子進程',))

        p1.daemon = True # 一定要放到p.start()之前
        p1.start()
        p2.start()

        print('主')




  • 互斥鎖

  • 互斥鎖:可以將要執行任務的部分代碼(只涉及到修改共享數據的代碼)變成串行

    實例


image.png


image.png



image.png



IPC機制:進程間通信,有兩種實現方式

1、pipe:管道(前面已經說過)
2、queue:pipe+鎖

image.pngimage.png





  • 線程

  • 什麼是線程
    進程其實不是一個執行單位,進程是一個資源單位
    每個進程內自帶一個線程,線程纔是cpu上的執行單位

        如果把操作系統比喻爲一座工廠
                在工廠內每造出一個車間===》啓動一個進程
                每個車間內至少有一條流水線===》每個進程內至少有一個線程
    
        線程=》單指代碼的執行過程(每個進程裏都有一個線程)
        進程-》資源的申請與銷燬的過程(向操作系統申請內存空間)



image.png




image.png




image.png


守護線程:守護線程會在本進程內所有非守護的線程都死掉了纔跟着死,即:守護線程其實守護的是整個進程的運行週期(進程內所有的非守護線程都運行完畢)


image.png



  • GIL全局解釋器鎖

  • 1 什麼是GIL
    GIL本質就是一把互斥鎖,那既然是互斥鎖,原理都一樣,都是讓多個併發線程同一時間只能
    有一個執行
    即:有了GIL的存在,同一進程內的多個線程同一時刻只能有一個在運行,意味着在Cpython中
    一個進程下的多個線程無法實現並行===》意味着無法利用多核優勢(運算)
    但不影響併發的實現

            GIL可以被比喻成執行權限,同一進程下的所以線程 要想執行都需要先搶執行權限
  • 2、爲何要有GIL
    因爲Cpython解釋器自帶垃圾回收機制不是線程安全的,也就是說如果沒有GIL的情況下,在給一個值
    賦值的情況下如果被垃圾回收機制回收了,那就會出現錯誤

  • 3 有兩種併發解決方案:
    多進程:計算密集型
    多線程:IO密集型(因爲我們以後用的都是基於網絡通信的套接字,而基於網絡通信就存在大量IO,於是我們用的最多的都是多線程的方式)


同步:提交完任務後(用'串行'的方式運行)就在原地等待,直到任務運行完畢後拿到任務的返回值,再繼續運行下一行代碼


from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
import time
import random


def task(n):
    print('%s run...' % os.getpid())
    time.sleep(10)
    return n ** 2


def parse(res):
    print('...')


if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    # pool.submit(task,1)
    # pool.submit(task,2)
    # pool.submit(task,3)
    # pool.submit(task,4)

    l = []
    for i in range(1, 5):
        future = pool.submit(task, i)
        l.append(future)
        # print(future)
        # print(future.result())

    pool.shutdown(wait=True)  # shutdown關閉進程池的入口
    for future in l:
        # print(future.result())
        parse(future.result())

    print('主')


異步:提交完任務(綁定一個回調函數)後根本就不在原地等待,直接運行下一行代碼,等到任務有返回值後會自動觸發回調函數


用多進程實現異步:


# from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
# import os,time,random
#
# def task(n):
#         print('%s run...' %os.getpid())
#         time.sleep(5)
#         return n**2
#
# def parse(future):
#         time.sleep(1)
#         res=future.result()
#         print('%s 處理了 %s' %(os.getpid(),res))
#
# if __name__ == '__main__':
#         pool=ProcessPoolExecutor(4)
#
#         start=time.time()
#         for i in range(1,5):
#                 future=pool.submit(task,i)
#                 future.add_done_callback(parse) # parse會在futrue有返回值時立刻觸發,
#                 # 並且將future當作參數傳給parse,
#                 # 由主進程來回調函數,運行時間爲  9.163417339324951
#         pool.shutdown(wait=True)
#         stop=time.time()
#         print('主',os.getpid(),(stop - start))




用多線程實現異步:


from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os
import time
import random


def task(n):
    print('%s run...' % current_thread().name)
    time.sleep(5)
    return n ** 2


def parse(future):
    time.sleep(1)
    res = future.result()
    print('%s 處理了 %s' % (current_thread().name, res))


if __name__ == '__main__':
    pool = ThreadPoolExecutor(4)
    start = time.time()
    for i in range(1, 5):
        future = pool.submit(task, i)
        future.add_done_callback(parse)  # parse會在futrue有返回值時立刻觸發,
        # 並且將future當作參數傳給parse
    pool.shutdown(wait=True)  # 哪個線程有時間了哪個線程就取回調函數,運行時間爲 6.003463268280029
    stop = time.time()
    print('主', current_thread().name, (stop - start))




協程:只有遇到io才切換到其他任務的協程才能提升單線程的執行效率



  • 1、協程只有遇到io才切換到其他任務的協程才能提升
    單線程實現併發
    在應用程序裏控制多個任務的切換+保存狀態(協程只有在IO在單線程下切換到另外一個任務才能提升效率)
    優點:
    應用程序級別切換的速度要遠遠高於操作系統的切換
    缺點:
    多個任務一旦有一個阻塞沒有切,整個線程都阻塞在原地
    該線程內的其他的任務都不能執行了

                一旦引入協程,就需要檢測單線程下所有的IO行爲,
                實現遇到IO就切換,少一個都不行,因爲一旦一個任務阻塞了,整個線程就阻塞了,
                其他的任務即便是可以計算,但是也無法運行了
  • 2、協程序的目的:
    想要在單線程下實現併發
    併發指的是多個任務看起來是同時運行的
    併發=切換+保存狀態


實例: 協程 服務端: pip3 install gevent
from gevent import spawn,monkey;monkey.patch_all() # 相當於給下面的代碼都打上標記,就都能識別

from socket import *
from threading import Thread


def talk(conn):
        while True:
            try:
                data = conn.recv(1024)
                if len(data) == 0: break
                conn.send(data.upper())
            except ConnectionResetError:
                break
        conn.close()


def server(ip, port, backlog=5):
    server = socket(AF_INET, SOCK_STREAM)
    server.bind((ip, port))
    server.listen(backlog)

    print('starting...')
    while True:
        conn, addr = server.accept()
        spawn(talk, conn, )  # 起一個協程,只要這個協程不死掉,進程也不會結束


if __name__ == '__main__':
    g = spawn(server, '127.0.0.1', 8080)  # 起一個進程
    g.join()  # 只要這個協程不死掉,進程也不會結束



客戶端:
from threading import Thread,current_thread
from socket import *
import os


    def task():
        client = socket(AF_INET, SOCK_STREAM)
        client.connect(('127.0.0.1', 8080))

        while True:
            msg = '%s say hello' % current_thread().name
            client.send(msg.encode('utf-8'))
            data = client.recv(1024)
            print(data.decode('utf-8'))

if __name__ == '__main__':
    for i in range(500):
        t = Thread(target=task)
        t.start()



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章