1. 程式人生 > 其它 >2022.4.19程序相關操作、方法、概念

2022.4.19程序相關操作、方法、概念

2022.4.19程序相關操作、方法、概念

  • 程式碼建立程序
  • join方法
  • 程序間資料預設隔離
  • 程序物件相關屬性和方法
  • 殭屍程序和孤兒程序
  • 守護程序
  • 互斥鎖(重要)

一、程式碼建立程序

問題:建立程序的方式有哪些?

(1)滑鼠雙擊桌面程式圖示

(2)程式碼建立

建立程序的本質:

在記憶體中申請一塊記憶體空間用於執行相應的程式程式碼

1、第一種(函式物件作為程序):

from multiprocessing import Process
import time

def task(name):
    print('%s is runing' % name)
    time.sleep(3)
    print('%s is over' % name)
    
if __name__ == '__main__':
    p = Process(target=task, args=('json',))  # 建立程序物件,指定為task,args指定資料,必須加逗號以元組形式傳入
    p.start()  # 告訴系統建立一個新的程序
    print('主程序')
注意:
"""
強調:不同的作業系統建立程序的要求不一樣
    在windows中建立程序是以匯入模組的方式進行 所以建立程序的程式碼必須寫在__main__子程式碼中
    否則會直接報錯 因為在無限制建立程序
    在linux和mac中建立程序是直接拷貝一份原始碼然後執行 不需要寫在__main__子程式碼中
"""

2、第二種(類物件作為程序):

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self, username):
        self.username = username
        super().__init__()
    def run(self):
        print('你好啊 小姐姐',self.username)
        time.sleep(3)
        print('get out!!!',self.username)
if __name__ == '__main__':
    p = MyProcess('tony')  # 建立程序物件
    p.start()  # 建立程序
    print('主程序')

# 主程序
# 你好啊 小姐姐 tony
# get out!!! tony
由此可見程序相當於進行非同步操作了

3、程序實現併發

將與客戶端通訊的程式碼封裝成一個函式
之後每來一個客戶端就建立一個程序專門做互動

服務端:

import socket
from multiprocessing import Process

# 封裝一個get_server,這個建立程序就不會反覆執行裡面的程式碼
# 從而避免報地址只允許使用一次的錯誤
def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    return server

# 將服務客戶端的程式碼封裝成函式(通訊程式碼)
def talk(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())

if __name__ == '__main__':
    server = get_server()
    while True:
        sock, addr = server.accept()
        p = Process(target=talk, args=(sock, ))
        p.start()

客戶端:

import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    client.send(b'hello big baby~')
    data = client.recv(1024)
    print(data.decode('utf8'))

二、join方法

作用:讓主程序程式碼等待子程序程式碼執行完畢再執行

from multiprocessing import Process
import time

def task(name, n):
    print(f'{name} is runing')
    time.sleep(n)
    print(f'{name} is over')
    
if __name__ == '__main__':
    p1 = Process(target=task, args=('jason', 1))
    p2 = Process(target=task, args=('tony', 2))
    p3 = Process(target=task, args=('kevin', 3))
    start_time = time.time()
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    end_time = time.time() - start_time
    print('主程序', f'總耗時:{end_time}')  # 主程序 總耗時:3.015652894973755,因為上面程序已經陸續開始,因此各程序是相互重疊的
    # 如果是一個start一個join交替執行 那麼總耗時就是各個任務耗時總和,因為join方法會讓子程序執行完再執行主程序

三、程序間資料預設隔離

引入:記憶體可以看成很多個小隔間,彼此互不干擾

from multiprocessing import Process

money = 999
def task():
    global money  # 區域性修改全域性不可變型別
    money = 666
    print(money)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()  # 確保子程序程式碼執行結束再列印money
    print(money)
結果:
666
999
 
"""預設隔離  但是可以通過一些技術打破,後面會了解"""

四、程序物件屬性和方法

如何檢視程序號?

# windows系統:
tasklist結果集中PID
# mac系統
ps -ef
# python程式碼檢視
  1.current_process函式
  	from multiprocessing import Process, current_process
        current_process().pid
 	# 獲取程序號的用處之一就是可以通過程式碼的方式管理程序
  	windows  	taskkill關鍵字
        mac/linux       kill關鍵字
  2.os模組
  	os.getpid()  # 獲取當前程序的程序號
        os.getppid()  # 獲取當前程序的父程序號
# 殺死子程序
terminate()
# 判斷子程序是否存活
is_alive()

五、殭屍程序和孤兒程序

1、殭屍程序

問題:為什麼主程序預設要等待子程序執行結束返回結果才會結束?

答:所有的子程序在執行結束之後都會變成殭屍程序(死了沒死透),

還保留著pid和一些執行過程中的記錄便於主程序檢視(短時間儲存),

這些資訊會被主程序回收時殭屍程序徹底死亡,以下情況將殺死執行結束後的殭屍程序
1.主程序正常結束
2.呼叫join方法

2、孤兒程序

狀態:子程序存活著,父程序意外死亡

子程序會被作業系統自動接管(eg:兒童福利院)

六、守護程序

守護程序:

即設定為守護程序的程序物件,會隨著守護的程序物件結束而結束

from multiprocessing import Process
import time

def task(name):
    print(f'大內總管:{name}正常活著')
    time.sleep(3)
    print(f'大內總管:{name}正常死了')

if __name__ == '__main__':
    p = Process(target=task, args=('趙公公',))
    p.daemon = True # 將p設為守護程序
    p.start()
    print('皇帝壽終正寢了啊')
    
# 結果:
皇帝壽終正寢了啊
大內總管:趙公公正常活著  # 只打印一個出來,因為主程序死亡,守護程序還沒執行完,直接結束

七、互斥鎖(重要)

引入(搶票問題):

為什麼手機上明明顯示還有餘票,但是點選購買的時候卻提示已經沒有票了,之後回到查詢頁面再查詢發現確實沒票了。

為什麼:

因為你某一時間開啟買票軟體檢視票數,看的是這一時間的資料,只要不重新整理不點選下一步,展示的永遠是這個時間的資料

那麼這個是如何實現的呢,這個就需要使用互斥鎖了。

程式碼模擬:

import json
from multiprocessing import Process
import time
import random

# ticket_data.json檔案內容:{"ticket_num": 0}

# 查票
def search(name):
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print(f'{name}查詢當前餘票:%s' % data.get('ticket_num'))
    
# 買票
def buy(name):
    # 1.點選買票是需要再次查票的 因為期間其他人可能已經把票買走
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    time.sleep(random.randint(1, 3))
    # 2.判斷是否還有餘票
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'ticket_data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print(f'{name}搶票成功')
    else:
        print(f'{name}搶票失敗 沒有餘票了')
        
def run(name):
    search(name)
    buy(name)
    
# 模擬多人同時搶票
if __name__ == '__main__':
    for i in range(1, 10):
        p = Process(target=run, args=('使用者:%s' % i,))
        p.start()
# 問題:
上面模擬了買票的基本邏輯,但是有一個問題,如果同時搶票的話,多個子程序獲得的資料都一樣,都會顯示搶票成功,造成資料的錯亂然,怎麼辦?
答:併發--->序列(犧牲效率保證資料安全)--->互斥鎖
# 注意:
1.互斥鎖並不能輕易使用 容易造成死鎖現象
2.互斥鎖只在處理資料的部分加鎖,不能什麼地方都加,嚴重影響程式的效率

互斥鎖:

from multiprocessing import Process, lock

mutex = lock()  # 定義互斥鎖
mutex.acquire()  # 搶鎖
mutex.release()  # 放鎖

結合搶票軟體,我們應該把鎖加在購買票的時候

def run(name, mutex):
    search(name)
    # 只需要把買票環節變成序列即可
    mutex.acquire()  # 搶鎖
    buy(name)
    mutex.release()  # 放鎖

注意:加鎖購買之後一定要放鎖,不然會一直卡在這個使用者手裡

鎖相關知識:

鎖相關知識
行鎖:針對行資料加鎖 同一時間只能一個人操作
表鎖:針對表資料加鎖 同一時間只能一個人操作
鎖的應用範圍很廣 但是核心都是為了保證資料的安全!!!