程序理論和實操

2022-08-06 21:01:19

1.什麼是程序

​ 程序是隻一個正在執行的程式. 是一個抽象的概念
​ 程序是起源於作業系統的. 是作業系統最核心的概念. 作業系統的其他概念都是圍繞程序展開的
​ 如果一個人說他精通程序. 那麼就是精通作業系統. 要麼就什麼都不知道

2.為什麼使用程序

​ 實現並行

3.並行. 並行. 序列的區別

​ 序列: 一個程式完完整整的執行結束. 才去執行下一個程式
​ 並行: 是偽並行. 即看起來是同時執行. 單個cpu+多道技術就可以實現並行(並行也屬於並行)
​ 並行: 真正意義上的同時執行. 需要具有多個cpu才行

4.子程序的建立

​ 建立子程序會先將程序中的全域性變數拷貝一份. 然後開啟自己的程序. 建立一個獨立的記憶體空間

​ 開啟程序之後將與原來的程序無關. 意味著全域性變數的改變子程序不會有任何變化

5.程序的狀態

​ 就緒. 阻塞. 執行
​ 三種狀態之間存在互動:

6.開啟程序的兩種方式

# 方式1:(常用)
from multiprocessing import Process
import time


def task(name):
    print('%s is running' % name)
    time.sleep(2)
    print('%s is done' % name)


if __name__ == '__main__':
    p = Process(target=task, args=('子程序', ))  # 引數傳入元組的格式
    p.start()  # 只是在作業系統中發出一個開啟子程序的訊號
    print('主')  # 主程序. 上一步已經傳送訊號. 但是創造子程序較慢


# 方式2: 物件導向(瞭解)
class Myprocess(Process):
    def __init__(self, name):
        super(Myprocess, self).__init__()  # 重用父程序方法
        self.name = name  # 可以起到更改name的效果

    def run(self):  # 必須要寫的類
        print('%s is running' % self.name)
        time.sleep(2)
        print('%s is done' % self.name)


if __name__ == '__main__':
    p = Myprocess('子程序')
    p.start()  # p.run()
    print('主')

7.殭屍程序和孤兒程序

殭屍程序:
    1. 父程序建立的子程序執行完程式碼都是變成殭屍程序. 等待著父程序呼叫wait或waitpid去回收pid等等. 也叫'收屍'
        此類殭屍程序是沒有害處的. 因為所有內容都會被回收
    2. 程式bug導致的父程序沒有回收pid. 使大量殭屍程序累加. 佔用pid並導致別的軟體應用無法開啟.
        這樣是有害的殭屍程序. 可以關機重啟來釋放所有記憶體

孤兒程序:
    父程序建立完子程序之後自己"死"了. 沒有回收子程序的pid及相關引數. 這樣的子程序是孤兒程序
    孤兒程序是無害處的. 因為孤兒程序最後會被__init__程序(pid=0)回收並釋放. 並不會形成累加

殺死程序: taskkill /F /PID pid編號
查詢程序: tasklist | findstr 關鍵詞

8.程序之間彼此隔離

# -*- encoding:utf-8 -*-
# @time: 2022/8/5 22:06
# @author: Maxs_hu
from multiprocessing import Process
import time

x = 100


def task():
    global x
    x = 0  # 在子程序的記憶體中修改x變數的值
    print('done')


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    time.sleep(2)  # 等待足夠長的時間保證子程序執行完畢
    print(x)

9.程序物件的方法或屬性

# -*- encoding:utf-8 -*-
# @time: 2022/8/5 22:33
# @author: Maxs_hu
from multiprocessing import Process
import time
import os

"""
join方法
"""


def task(name, t):
    print('%s is running' % name)
    time.sleep(t)
    print('%s is done' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('子程序1', 1))
    p2 = Process(target=task, args=('子程序2', 2))
    p3 = Process(target=task, args=('子程序3', 3))

    # 這樣可以模擬並行操作. 序列用process不如直接呼叫. 因為process還多了向作業系統發訊號以及切換程序的操作
    p1.start()
    p2.start()
    p3.start()

    p1.join()  # 1s # 等待子程序p1完成之後. 在執行父程序. 且子程序執行完成會被回收
    p2.join()  # 1s
    p3.join()  # 1s

    # 子程序的pid已經被回收. 但是還可以看到. 變成了一個free狀態

    print('main')


# 程式碼精簡
def task(name, t):
    print('%s is running' % name)
    time.sleep(t)
    print('%s is done' % name)


if __name__ == '__main__':
    p_ls = []
    for i in range(1, 4):
        p = Process(target=task, args=('子程序%s' % i, i))
        p_ls.append(p)  # 將範例化物件加入列表中
        p.start()  # 只是作業系統發訊號的操作

    for p in p_ls:
        p.join()  # 直接呼叫範例化繫結方法
        # 這裡也不需要按順序去join. 因為執行總時間 = 任務等待時間最長的+程序之間的切換時間

"""
pid方法
"""


# pid方法
def task(n):
    print('%s is running ' % os.getpid())
    time.sleep(n)
    print('%s is done ' % os.getpid())


if __name__ == '__main__':
    p1 = Process(target=task, args=(10,))
    # print(p1.pid)
    p1.start()
    print(p1.pid)  # 父程序內檢視子pid的方式
    print('主')


# ppid方法
def task():
    print('自己的id:%s 父程序的id:%s ' % (os.getpid(), os.getppid()))
    time.sleep(200)


if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    print('主', os.getpid(), os.getppid())  # 這裡ppid拿到的是pycharm的pid
    # 爹=》主--》兒子

10.守護行程

# -*- encoding:utf-8 -*-
# @time: 2022/8/6 10:08
# @author: Maxs_hu

"""
什麼是守護行程:
    守護行程相當於 "子程序"
    守護行程會伴隨著子程序的程式碼執行完畢後而死掉

為什麼使用守護行程:
    關鍵字兩個:
        1.程序:
            當父程序需要一個任務去並行執行. 需要將該任務放在一個子程序中
        2.守護:
            當該子程序中內的程式碼在父程序程式碼執行完畢後就沒有存在的意義了.
            就要應該將該程序設定為守護行程. 並在父程序程式碼結束後死掉
    在生產者消費中模型中會用到
"""

from multiprocessing import Process
import time


def task(name, t):
    print('%s is running' % name)
    time.sleep(t)
    print('%s is done' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('守護行程', 2))
    p2 = Process(target=task, args=('子程序', 3))
    p1.daemon = True  # 守護行程

    p1.start()
    p2.start()

    print('main')

    

# 守護行程的案例
def foo():
    print(123)
    time.sleep(1)
    print('end123')


def bar():
    print(456)
    time.sleep(3)
    print('end456')


if __name__ == '__main__':
    p1 = Process(target=foo)
    p2 = Process(target=bar)
    p1.daemon = True  # 設定守護行程

    p1.start()
    p2.start()

    print('main')

# 執行結果可能會出現各種格式.主要依據機器效能.並根據並行思維去思考
"""
main
456
end456
"""

11.互斥鎖

# -*- encoding:utf-8 -*-
# @time: 2022/8/6 11:16
# @author: Maxs_hu
"""
互斥鎖和join的區別:
    互斥鎖:
        可以將執行任務的部分程式碼(只涉及到修改共用資料的程式碼)程式設計序列
    join:
        將所有執行任務的程式碼變成序列

互斥鎖是增加執行時間去保護共用資料安全.
互斥鎖是什麼可以舉個例子: 假設你和別人合租房. 可能會與很多公共資源. 有些是可以同時使用. 有些不能同時使用.
    那麼不能同時使用的(比如廁所)可以加上一把鎖. 先到先得. 等使用完成了下一個人再繼續搶鎖. 那麼這個鎖在我們的程式中就是互斥鎖
"""
from multiprocessing import Process, Lock
import time
import os
import json


def check():
    time.sleep(1)
    with open('db.txt', 'rt', encoding='utf8') as f:
        dic = json.load(f)  # 將檔案資料讀出. 注意讀出json資料一定要使用雙引號
    print('%s 檢視餘票[%s]' % (os.getpid(), dic['count']))


def get():
    time.sleep(2)
    with open('db.txt', 'rt', encoding='utf8') as f:
        dic = json.load(f)
    if dic['count'] > 0:
        dic['count'] -= 1
        with open('db.txt', 'wt', encoding='utf8') as f:
            json.dump(dic, f)  # 將資料寫入
        print('%s 已購買餘票' % os.getpid())
    else:
        print('%s 沒有餘票' % os.getpid())


def task(mutex):
    # 檢視餘票
    check()
    # 購票
    mutex.acquire()  # 互斥鎖不能連續的acquire. 只能是release之後才能進行acquire
    get()
    mutex.release()
    # with mutex():
    #     get()


if __name__ == '__main__':
    mutex = Lock()
    for i in range(10):  # 模擬十個人搶票
        p = Process(target=task, args=(mutex,))
        p.start()

12.IPC機制(程序通訊)

# -*- encoding:utf-8 -*-
# @time: 2022/8/6 13:12
# @author: Maxs_hu

# IPC: 程序間通訊
# PIPE: 管道
# Queue: pipe+鎖

from multiprocessing import Queue

q = Queue(3)  # 先進先出
q.put('123', block=True, timeout=3)
q.put(['python', 'go'], block=True, timeout=3)
q.put({'security': 1000}, block=True, timeout=3)
# q.put(123, block=True, timeout=3)  # 超過佇列的長度. 會報錯queue.Full

print(q.get())  # 從佇列中get也可以加引數block. timeout
print(q.get())
print(q.get())


from multiprocessing import Queue
import time

q = Queue(3)
q.put_nowait('123')  # 相當於q.put('123', block=False)
q.put_nowait(['python', 'Java'])
q.put_nowait({'security': 6000})

time.sleep(1)  # ???

print(q.get_nowait())
print(q.get_nowait())
print(q.get_nowait())
# print(q.get_nowait())  # 佇列中沒有資料也會報錯 -> _queue.Empty

13.生產者消費者模型

# -*- encoding:utf-8 -*-
# @time: 2022/8/6 16:37
# @author: Maxs_hu
"""
1. 什麼是生產者消費者模型:
    生產者: 程式中負責生產資料的程式碼塊
    消費者: 程式中負責處理資料的程式碼塊

    生產者->共用的媒介(佇列)<-消費者

2. 為何使用:
    實現了生產者消費者的解耦合. 生產者可以不停的生產. 消費者可以不停的消費. 提升了程式執行效率

    什麼時候使用:
        當程式中存在明顯的兩類任務. 一類負責生產資料. 另一類負責處理資料
        我們此時就應該考慮使用生產者消費者模型去提高程式執行效率
        一般情況下. 消費者的速度會大於生產者
"""


# 初始模型(有bug) -> 在佇列被消費者去空之後. 消費者會死等下一波資料. 導致主程序無法回收消費者. 主程序無法停止
import time
import os
import random
from multiprocessing import Process, Queue


def producer(q):
    for i in range(10):
        res = '包子%s' % i
        time.sleep(random.randint(1, 3))
        # 將資料放入佇列
        q.put(res)
        print('\033[45m%s 生產了包子%s\033[0m' % (os.getpid(), i))  # \033[45m可以在終端有顏色列印


def consumer(q):
    while True:
        # 從佇列中迴圈拿資料
        res = q.get()
        time.sleep(random.randint(1, 3))
        print('\033[46m%s 吃了 %s\033[0m' % (os.getpid(), res))


if __name__ == '__main__':
    q = Queue()
    # 建立生產者們
    p = Process(target=producer, args=(q, ))
    # 建立消費者們
    c = Process(target=consumer, args=(q, ))

    p.start()
    c.start()

    print('main')


# 生產者消費者 終極模型
# 1. 使用JoinableQueue
# 2. 消費者設定守護行程. 伴隨著主程序的程式碼執行完畢自己死掉
# 3. 先p.join()等待生產者生產完畢. 再q.join()等待佇列被取乾淨
from multiprocessing import Process, JoinableQueue
import time
import random


def producer(name, food, q):
    for i in range(3):
        res = f'{food}{i}'
        time.sleep(random.randint(1, 3))
        print(f'\033[47m{name}生產了{res}\033[0m')
        q.put(res)


def consumer(name, q):
    while True:
        res = q.get()
        time.sleep(random.randint(1, 3))
        print(f'\033[43m{name}吃了{res}\033[0m')
        q.task_done()


if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer, args=('Max_hu', 'pizaa', q))
    p2 = Process(target=producer, args=('Mokeke', 'pig', q))
    p3 = Process(target=producer, args=('xiaoergu', 'xiang', q))

    c1 = Process(target=consumer, args=('longge', q))
    c2 = Process(target=consumer, args=('chunge', q))
    c1.daemon = True  # 設定守護行程. 主執行緒執行完畢即代表著消費者也沒有存在的意義了
    c2.daemon = True

    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()

    # 等待生產者全部生產完畢
    p1.join()
    p2.join()
    p3.join()

    q.join()  # 等待佇列被取乾淨
    # q.join() 意味著:
    # 主執行緒程式碼執行完畢 --> (生產者執行完畢) + 佇列中的資料也被取乾淨了 --> 消費者沒有存在的意義了.

補充

部分作業系統原理

1. 什麼是作業系統:
    精簡來說: 作業系統是一個協調.管理和控制計算機控制計算機硬體資源和軟體資源的控制程式(硬體和軟體之間)
    作用:
        1. 將硬體複雜的介面封裝成簡單的介面. 給使用者或者應用程式使用
        2. 讓多個應用程式對硬碟的競爭變的有序

2、序列:
    一個任務完完整整地執行完畢後,才能執行下一個任務

3、並行:
    看起來多個任務是同時執行的即可,單核也可以實現並行. 一個任務沒有執行完就去執行下一個任務(將io的時間節省下來)

4、並行:
    真正意義上多個任務的同時執行,只有多核才實現並行

5、cpu的功能:
    cpu是用來做計算,cpu是無法執行IO操作的,一旦遇到io操作,應該讓cpu去執行別的任務

6、多道技術(*****). 三代計算器還沒有實現程序之間的物理層面的隔離
    1、空間上的複用 -> 多個程序共用一個記憶體條,各程序之間要通過物理層面隔離. 為cpu在多個程序之間的切換提供鋪墊
    2、時間上的複用 -> 多個程序複用同一個cpu的時間
        cpu遇到IO切換:可以提升效率
        一個程序佔用cpu時間過長或者說有另外一個優先順序更高的程序,也會切走:為了實現並行效果不得已而為之,反而會降低程式的執行效率

7、我們寫的攜程儘量減少io操作. 只要涉及到io. cpu就會切換到別的程序.等io執行完畢. 這樣就會浪費時間
    怎麼減少io操作. 優化到時候最後就是減少與硬碟打交道. 從硬碟中讀取資料就是最典型的io操作