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)