1. 程式人生 > >DEVOPS-01多程序、多執行緒程式設計

DEVOPS-01多程序、多執行緒程式設計

一、多執行緒程式設計

1.1 forking工作原理

1.1.1 什麼是forking

1. fork(分岔)在Linux系統中使用非常廣泛
2.  當某一命令執行時,父程序(當前程序)fork出一個子程序
3.  父程序將自身資源拷貝一份,命令在子程序中執行時,就具有和父程序完全一樣的執行環境

1.1.2 程序的生命週期

1. 父程序fork出子程序並掛起
2. 子程序執行完畢後,釋放大部分資源並通知父程序,
這個時候,子程序被稱作殭屍程序
3. 父程序獲知子程序結束,子程序所有資源釋放

1.1.3 殭屍程序

1. 殭屍程序沒有任何可執行程式碼,也不能被排程
2. 如果系統中存在過多的殭屍程序,將因為沒有可用的程序號而導致系統不能產生新的程序
3. 對於系統管理員來說,可以試圖殺死其父程序【這個時候殭屍程序會變成孤兒程序,由systemd進行管理】或重啟系統來消除殭屍程序

1.2 forking程式設計

1.2.1 forking程式設計基本思路

1. 需要使用os模組

2. os.fork()函式實現forking功能

3. python中,絕大多數的函式只返回一次,os.fork將返回兩次

4. 對fork()的呼叫,針對父程序返回子程序的PID;對於子程序,返回PID 0

5.因為所有的父子程序擁有相同的資源,所以在編寫程式時要避免資源衝突

網路程式設計思路如下:    
pid    =    os.fork()                                                           #實現forking    
if    pid:                                                                           #在父程序中關閉子程序連線    
                close_child_conn                                            #接著處理其他的連線請求    
                handle_more_conn    
else:                                                                                #子程序關閉父程序連線,響應當    
                close_parent_conn                                         #前的使用者連線    
                process_this_conn    

1.2.2 forking基礎應用

編寫一個forking指令碼
1.  在父程序中列印“In parent”然後睡眠10秒
2.  在子程序中編寫迴圈,迴圈5次,輸出當系統時間,
每次迴圈結束後睡眠1秒
3.  父子程序結束後,分別列印“parent exit”和“child exit”

import os
import time
pid=os.fork()
if pid:
    print('In parent')
    time.sleep(10)
    print('parent exit')
else:
    for i in range(5):
        print(time.strftime('%Y-%m-%d %H:%M:%S'))
        time.sleep(1)
    print('child exit')

 1.2.3 掃描存活的主機

1.  通過ping測試主機是否可達
2.  如果ping不通,不管什麼原因都認為主機不可用
3.  通過fork方式實現併發掃描

import subprocess
import os
import time

def ping(host):
    rc=subprocess.call('ping -c2 %s &> /dev/null' % host,shell=True)
    if rc == 0:
        print('%s:up'% host)
    else:
        print('%s:down'% host)
if __name__ == '__main__':
    ips=['176.130.10.%s' % i for i in range(1,255)]
    for i in ips:
        pid=os.fork()
        if pid==0:
            ping(i)
            exit(0)

1.2.4 使用輪詢解決zombie問題

1.  父程序通過os.wait()來得到子程序是否終止的資訊
2.  在子程序終止和父程序呼叫wait()之間的這段時間,子程序被稱為zombie(殭屍)程序
3.  如果子程序還沒有終止,父程序先退出了,那麼子程序會持續工作。系統自動將子程序的父程序設定為
systemd程序,systemd將來負責清理殭屍程序

4. python可以使用waitpid()來處理子程序
5. waitpid()接受兩個引數,第一個引數設定為-1,表示與wait()函式相同;第二引數如果設定為0表示掛起父程序,

直到子程式退出,設定為1表示不掛起父程序
6.  waitpid()的返回值:如果子程序尚未結束則返回0,否則返回子程序的PID

 os
# import time
# print('staring')
# pid=os.fork()
# if pid:
#     print('in parent...')
#     print(os.waitpid(-1,0))  #-1是固定的,0表示掛起父程序,直到子程序被處理
#     print('go on ...')       #子程序變成了殭屍程序【子程序執行完畢】,被父程序處理後執行
# else:
#     print('in child')
#     time.sleep(15)

import os
import time

print('staring')
pid = os.fork()
if pid:
    print('in parent...')
    print(os.waitpid(-1, 1))  # -1是固定的,1表示不掛起父程序
    print('go on ...')        #立即執行
else:
    print('in child')
    time.sleep(15)
    print('child go on ...')

1.2.5 forking伺服器

1. 在網路伺服器中,forking被廣泛使用
2. 如果伺服器需要同時響應多個客戶端,那麼forking
是解決問題最常用的方法之一
3. 父程序負責接受客戶端的連線請求
4. 子程序負責處理客戶端的請求

1.2.6 利用fork建立TCP伺服器

  • 編寫TCP伺服器

1.  伺服器監聽在0.0.0.0的21567埠上
2.  收到客戶端資料後,將其加上時間戳後回送給客戶端
3.  如果客戶端發過來的字元全是空白字元,則終止與客戶端的連線
4.  伺服器能夠同時處理多個客戶端的請求
5.  程式通過forking來實現

import socket
from time import strftime
import os
class TcpTimeServ:
    def __init__(self, host='', port=12345):
        self.addr = (host, port)
        self.serv = socket.socket()
        self.serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.serv.bind(self.addr)
        self.serv.listen(1)

    def handle_child(self, c_sock):
        while True:
            data = c_sock.recv(1024)
            if data.strip() == b'quit':
                break
            data = '[%s] %s' % (strftime('%Y-%m-%d %H:%M:%S'), data.decode())
            c_sock.send(data.encode())
        c_sock.close()

    def mainloop(self):
        while True:
            try:
                cli_sock,cli_addr=self.serv.accept()
            except:
                break
            pid=os.fork()

            if pid:
                cli_sock.close()  #父程序關閉客戶端套接字
                while True:
                    rc=os.waitpid(-1,1)
                    print(rc)
                    if rc[0]==0:
                        break
            else:
                self.serv.close()  #子程序關閉服務端套接字
                self.handle_child(cli_sock)
                exit(0)

if __name__ == '__main__':
    s=TcpTimeServ()
    s.mainloop()

1.3 使用多程序模組multiprocessing中的process模組建立程序

1.3.1 在python中開啟第一個子程序

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    print('我是子程序')

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    time.sleep(1)
    print('執行主程序的內容了')

1.3.2 join方法

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)
    print('我是子程序')


if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
    print('我是父程序')

1.3.3 多個程序同時執行(子程序的執行順序不是根據啟動順序決定的)

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)

if __name__ == '__main__':
    p_lst = []
    start = time.time()
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)            #把程序加入列表中
    for i in p_list:
        p.join(i)
    end = time.time()
    print(end-start)
    

附:詳情可以參考:https://www.jianshu.com/p/3b28c9b5dec1

二、多執行緒程式設計

2.1 程序和執行緒

2.1.1 什麼是程序

程序和執行緒的根本區別:程序是作業系統資源分配的基本單位,而執行緒是任務排程和執行的基本單位

1.  計算機程式只不過是磁碟中可執行的、二進位制(或其它型別)的資料
2.  程序(有時被稱為重量級程序)是程式的一次執行
3.  每個程序都有自己的地址空間、記憶體以及其它記錄其執行軌跡的輔助資料
4.  作業系統管理在其上執行的所有程序,併為這些程序公平地分配時間資料空間(程式上下文)

2.1.2 什麼是執行緒

同一個程序(程式)中有多個執行緒同時執行(通過CPU排程,在每個時間片中只有一個執行緒執行)

執行緒可以看做輕量級的程序,同一類執行緒共享程式碼和資料空間,每個執行緒都有自己獨立的執行棧和程式計數器(PC),執行緒之間切換的開銷小。

2.2 多執行緒程式設計

2.2.1 多執行緒相關模組

1. 多執行緒程式設計有兩種方法

方式一:傳遞函式給threading模組的Thread類是介紹的第一種方法

Thread物件使用start()方法開始執行緒的執行,使用join()方法掛起程式,直到執行緒結束

方式二:傳遞可呼叫類給Thread類是第二種方法

2.2.2 掃描存活主機

1.  通過ping測試主機是否可達
2.  如果ping不通,不管什麼原因都認為主機不可用
3.  通過多執行緒方式實現併發掃描

方式一:

import subprocess
import threading
import time

def ping(host):
    rc=subprocess.call('ping -c2 %s &> /dev/null' % host,shell=True)
    if rc == 0:
        print('%s:up'% host)
    else:
        print('%s:down'% host)
if __name__ == '__main__':
    ips=['176.130.10.%s' % i for i in range(1,255)]
    start=time.time()
    for i in ips:
        t=threading.Thread(target=ping,args=(i,))   #target是執行緒執行時要呼叫的函式,args是傳給target函式的引數
        t.start()   #執行target(args)

方式二:

import subprocess
import threading
import time

class Ping:
    def __init__(self,host):
        self.host=host
    def __call__(self):
        rc = subprocess.call('ping -c2 %s &> /dev/null' % self.host, shell=True)
        if rc == 0:
            print('%s:up' % self.host)
        else:
            print('%s:down' % self.host)

if __name__ == '__main__':
    ips = ['176.130.3.%s' % i for i in range(1, 255)]
    start=time.time()
    for ip in ips:
        t=threading.Thread(target=Ping(ip))
        t.start()  #相當於呼叫 target()
    end=time.time()
    print(end-start)