網絡編程

第二十七章

網絡編程

  1. 概念

    網絡架構
    • C/S:qq 微信 瀏覽器 英雄聯盟 須要安裝
      • C:client客戶端
      • S:server服務端
    • B/S:百度 淘寶 碼雲 只要在瀏覽器輸入網址就能夠直接使用
      • B:browser瀏覽器
      • S:server服務端
    • B/S架構中的瀏覽器 也是客戶端的一種
      • B/S是C/S架構中的一種
    • B/S更好:更節省資源 不用更新 不依賴環境
      • 統一了全部web程序的入口
    • C/S架構:安全性 程序比較龐大
    • 移動端
      • app
      • 微信小程序:統一了全部web程序的入口
      • 支付寶:統一了全部和錢有關的事
  2. mac地址

    • 是一個物理地址
    • 惟一的表示你的網絡設備
  3. IP地址

    • 是一個邏輯地址
    • 是能夠根據你的位置變化發生改變的
    • 可以在廣域網中快速的定位你
  4. ipv4地址

    • 4位點分十進制
    • 0.0.0.0 - 255.255.255.255
    • 2**32
  5. 公網和內網

    • 公網0.0.0.0 - 255.255.255.255(不包含保留字段的ip)
      • 你可以在任意一個地方去訪問的ip地址
    • 內網 全部的內網ip都要使用保留字段
      • 只能在一個區域內使用,出了這個區域就用不了了
      • 192.168.0.0 - 192.168.255.255 2**16
      • 10.0.0.0 - 10.255.255.255 2**20
      • 172.16.0.0 - 172.32.255.255 2**24
  6. 局域網和廣域網的區別:相對於誰

  7. 路由器和交換機

    • 交換機完成局域網內通訊
      • 經過ip找mac地址:arp協議
      • 單播 廣播 組播
    • 路由器完成局域網間通訊
  8. 網關ip

    • 全部與局域網外部通訊的時候所過的關口
    • 全部的請求都會在這裏換上網關ip對外通訊
  9. 子網掩碼

    • 能夠判斷要尋找的機器是否是在一個局域網中
    • 255.0.0.0
    • 255.255.0.0
    • 255.255.255.0
    • ip和子網掩碼 按位與運算
    • 192.168.13.26 255.255.0.0
      • 11000000.10101000.00001101.00011010
      • 11111111.11111111.00000000.00000000
      • 11000000.10101000.00000000.00000000 = 192.168.0.0
    • 192.168.12.134 255.255.0.0
      • 11000000.10101000.00001100.10000110
      • 11111111.11111111.00000000.00000000
      • 11000000.10101000.00000000.00000000 = 192.168.0.0
  10. ipv6
  • 6位冒分十六進制
  • 0:0:0:0:0:0 - FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
  • mac ip 定位到一臺機器
  1. port端口
    • 0-65535
    • ip + port可以惟一的確認網絡上的一臺機器上的一個服務
  2. 協議
    • arp協議:地址解析協議,經過一臺機器的ip地址找到mac地址
      • 交換機 廣播 單播
    • ip協議:ipv4 ipv6
      • 內網保留字段
      • 特殊ip地址
        • 127.0.0.1 本地迴環地址
        • 0.0.0.0 表示你的全部網卡的地址
  3. 簡單的socket通訊
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
    a = input('輸入內容,q退出')
    if a.upper() == 'Q':
        sk.send(a.encode('utf-8'))
        break
    else:
        i = sk.recv(1024).decode('utf-8')
        if i.upper() == 'Q':
            break
        print(i)
        sk.send(a.encode('utf-8'))
sk.close()
server端
import socket
sk = socket.socket()			###實例化一個對象
sk.bind(('127.0.0.1',9001))		###綁定一個地址 ip + 端口
###127.0.0.1 永遠標識本機地址
###不過交換機 排除一些網絡問題
sk.listen()						###開始監聽客戶的請求
conn,addr = sk.accept()			###接收一個鏈接請求
while True:
    a = input('請輸入內容,q退出')
    if a.upper() == 'Q':
        conn.send(a.encode('utf-8'))
        break
    else:
        conn.send(a.encode('utf-8'))
        i = conn.recv(1024).decode('utf-8')
        if i.upper() == 'Q':
            break
        print(i)

conn.close()					###關閉與客戶端的鏈接
sk.close()						###關閉服務端

第二十八章

  1. socket部分涉及的協議 -- 面試高頻

    1. osi七層協議

      1. 應用層

      2. 表示層

      3. 會話層

      4. 傳輸層

      5. 網絡層

      6. 數據鏈路層

      7. 物理層
    2. osi五層協議 互聯網傳輸的通用協議

      1. 五層:應用層 內容 http https

      2. 四層:傳輸層 端口 四層交換機 四層路由器 UDP TCP 協議

      3. 三層:網絡層 ip ipv4 ipv6 路由器 三層交換機

      4. 二層:數據鏈路層 mac arp協議 網卡 二層交換機

      5. 一層:物理層

    3. TCP/IP

      1. 網絡層 arp

    4. TCP協議 上傳\下載\發郵件

      1. 可靠:消息傳遞過程當中不會丟失

        1. 創建鏈接

          1. 三次握手
        2. 消息傳遞

          1. 可靠傳輸
        3. 斷開鏈接

          1. 四次揮手
      2. 面向鏈接

      3. 速度慢

      4. 傳遞的數據無限

      5. 實現全雙工通訊

      6. 流式傳輸,數據與數據之間沒有明顯的界限,因此可能會出現黏包現象

      7. 面試回答三次握手和四次揮手

        1. 三次握手是tcp協議創建鏈接的過程

        2. 由客戶端發起一個syn請求,服務器收到並回復(syn、ack),客戶端收到ack和syn後再回復一個ack

        3. 在原生的socket代碼中三次握手是由accept connect

        4. 四次揮手是由close

    5. UDP協議 即時通信工具 在線播放視頻

      1. 不可靠:消息沒有實時性

      2. 面向數據報

      3. 速度快

      4. 傳遞的數據有限

      5. 一對一 一對多 多對多

    server端
    from socket import  socket,SOCK_DGRAM
    sk = socket(type= SOCK_DGRAM)
    sk.bind(('127.0.0.1',9001))
    msg,addr = sk.recvfrom(1024)
    
    
    client端
    from socket import socket,SOCK_DGRAM
    sk = socket(type= SOCK_DGRAM)
    ip = ('127.0.0.1',9002)
    sk.sendto(msg,ip)

第二十九章

TCP協議

黏包現象

當多條消息發送時接收變成了一條或者出現接收不許確的狀況

黏包現象會發生在發送端

兩條消息間隔時間短,長度短,就會把兩條消息在發送以前就拼在一塊兒

節省每一次發送消息回覆的網絡資源

黏包現象會發生在接收端

多條消息發送到緩存端,但沒有被及時接收,或者接收的長度不足一次發送的長度

數據與數據之間沒有邊界

本質:發送的每一條數據之間沒有邊界

黏包問題解決辦法:

計算要發送的數據字節長度

把字節的長度編程4字節

發送4字節

發送數據

server端
import struct
import socket

def proto_send(msg):
    msg = msg.encode('utf-8')
    len_msg = len(msg)
    proto_len = struct.pack('i', len_msg)  		###把字節的長度編程4字節,i表明int
    conn.send(proto_len)
    conn.send(msg)

sk = socket.socket()
sk.bind(('192.168.12.26',9001))
sk.listen()

conn,addr = sk.accept()
msg1 = 'hello'
msg2 = 'world'
proto_send(msg1)
proto_send(msg2)



client端
import struct
import socket

sk = socket.socket()

def proto_recv():
    len_msg = sk.recv(4)
    len_msg = struct.unpack('i', len_msg)[0]		###解包取元組中的第0個元素就是數字 
    msg = sk.recv(len_msg)
    return msg

sk.connect(('192.168.12.26',9001))
for i in range(1000000):2*i
msg1 = proto_recv()
print(msg1)
msg2 = proto_recv()
print(msg2)

簡單的文件傳輸:

server端
import socket

sk = socket.socket()
sk.bind(('192.168.12.26',9002))
sk.listen()

conn,addr = sk.accept()
with open(r'D:\Python_s25\day29\1.內容回顧.py','rb') as f:
    content = f.read()
    conn.send(content)

conn.close()
sk.close()



client端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9002))

content = sk.recv(40960)
with open(r'內容回顧.py','wb') as f:
    f.write(content)

sk.close()

struct模塊:

import struct

ret = struct.pack('i',197274000)
print(ret)

# 可以把一個任意大小的數據 轉換成固定的 4個字節

res = struct.unpack('i',b'\x90)\xc2\x0b')
print(res[0])

# -2147483648 ~ +2147483647  # 最大2g

第三十章

  1. TCP做業講解

    1. 顯示序號能夠用枚舉列表enumerate
    2. 信息傳遞最好用字典,josn傳遞
    3. 調用函數能夠用反射對象是 sys.modules(name)
    4. 先寫註釋
  2. 驗證客戶端合法性(統一用戶)客戶端添加祕鑰進行加密

    1. 登陸 : 只要有個性化設計的時候就須要登陸(不一樣用戶)
    2. 登陸和合法性驗證二選一,若是作登陸功能就不須要作合法性驗證了
    server端
    import os
    import socket
    import hashlib
    SECRET_KEY = b'alexbigsb'
    
    def check_client(conn):
        randbytes = os.urandom(32)
        conn.send(randbytes)
    
        md5 = hashlib.md5(SECRET_KEY)
        md5.update(randbytes)
        code = md5.hexdigest()
        code_cli = conn.recv(32).decode('utf-8')
        return code == code_cli
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        if not check_client(conn):continue
        print('進程正常的通訊了')
    
    
    一種加密方式
    # import os
    # import hmac  # hashlib
    # randbytes = os.urandom(32)
    # mac = hmac.new(SECRET_KEY,randbytes)
    # ret = mac.digest()
    # print(ret)
    client端
    import os
    import socket
    import hashlib
    SECRET_KEY = b'alexbigs'
    
    def check_client():
        randbytes = sk.recv(32)
        md5 = hashlib.md5(SECRET_KEY)
        md5.update(randbytes)
        code = md5.hexdigest().encode('utf-8')
        sk.send(code)
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    check_client()
    print('正常的客戶端通訊')
  3. 動態進度條

    def processBar(num, total):
        """
        進度條
        :param num:
        :param total:
        :return:
        """
        rate = num / total
        rate_num = int(rate * 100)
        if rate_num == 100:
            r = '\r%s>%d%%\n' % ('=' * rate_num, rate_num,)
        else:
            r = '\r%s>%d%%' % ('=' * rate_num, rate_num,)
        sys.stdout.write(r)
    processBar(1024,20480)
  4. 併發的socketserver

    1. BaseRequestHandler 基礎請求操做符
    2. ThreadingTCPServer 線程實現的基於tcp協議的server
    server端
    from socketserver import BaseRequestHandler,ThreadingTCPServer
    class Myserver(BaseRequestHandler):
        def handle(self):
            conn = self.request
            內容
    server = ThreadingTCPServer(('127.0.0.1',9001),Myserver)
    server.serve_forever()

第三十一章

操做系統基礎

發展:

穿孔卡片

高速磁帶

多道操做系統

第一次提出了多個程序能夠同時在計算機中被計算

1.遇到IO就讓出CPU

2.把CPU讓給其餘程序,讓其餘程序可以使用CPU

3.CPU的讓出這件事,佔用時間 可是不多

4.兩個程序來回在CPU上切換,不會亂

每一個程序有獨立的內存空間
每一個程序在切換先後會把當前程序的狀態記錄下來

CPU計算和不計算(IO)操做

IO操做(網絡操做/文件操做):輸入輸出:相對內存

阻塞sleep\input\recv\recvfrom\accept 是不須要CPU參與的

對文件的讀取:對硬盤的操做一次讀取至關於CPU執行90W條代碼

input:向內存輸入數據

讀\load\input\recv\recvfrom\accept\connect\close

Output:從內存輸出數據

寫\dump\print\send\sendto\accept\connect\close

全部的IO操做本質都是文件操做

input\print input是寫入文件,而後經過讀取文件把輸入的內容加載到內存

print是直接寫入文件,而後經過文件展現給用戶看

socket中的交互方法:都是文件操做

send是向緩存文件中寫

recv是從緩存文件中讀

也就是說只要涉及到IO操做 至少就是一個0.009s = 就是CPU執行90W條代碼的時間,CPU每秒能處理五億條python代碼

把代碼拆分紅python解釋器指令

import dis
a = 1
def func():
    global a
    a+=1
dis.dis(func)

一、先來先服務算法

二、短做業優先算法

三、時間片輪轉算法 -- 分時操做系統

1w = 0.00005s

一、時間片到了才讓出cpu

二、cpu的讓出這件事 佔用時間

三、下降工做效率,提升了用戶體驗

進程概念

程序與進程(計算機中最小的資源分配單位)

運行中的程序就是進程

進程與進程之間的數據時隔離的

線程(計算機中能被操做系統調度的最小單位)

每一個程序執行到哪一個位置是被記錄下來的

在進程中,有一條線程是負責具體的執行程序的

進程的調度(由操做系統完成的)

被操做系統調度的,每一個進程中至少有一個線程

短做業優先算法

先來先服務算法

時間片輪轉算法

多級反饋算法(隊列 包括前三個算法)

進程的啓動 銷燬

啓動:交互 在一個進程中啓動另外一個 開機自啓

負責啓動一個進程的程序,被稱爲一個父進程

被啓動的進程 被稱爲一個子進程

銷燬:交互 被其餘進程殺死(在父進程結束子進程)出錯進程結束

父子進程

父進程開啓子進程

父進程還要負責對結束的子進程進行資源回收

不回收的子進程就是殭屍進程

父進程結束以後子進程就成爲孤兒進程 由操做系統接手

進程id -- > processid -- > pid

在同一臺機器上 同一個時刻 不可能出現兩個重複的進程id

進程id不能設置,是操做系統隨機分配的

進程id隨着屢次運行一個程序可能會被屢次分配,每一次都不同

進程的三狀態圖

就緒 ready 運行run 阻塞bolck

查看當前進程的pid的父進程的pid

import os
import time

print(os.getpid())	#本進程id
print(os.getppid())  # parent process id	#父進程id
time.sleep(100)

模塊multiprocessing模塊 :內置模塊

multiple 多元化的

processing 進程

把全部和進程相關的機制都封裝在multiprocessing模塊中了

import os
import time
from multiprocessing import Process

def func():
    '''
    在子進程中執行的func
    :return:
    '''
    print('子進程 :',os.getpid(),os.getppid())
    time.sleep(3)
if __name__ == '__main__':
    p = Process(target=func)
    p.start()
    print('主進程 :',os.getpid())

程序的執行狀態

並行:多個程序同時被cpu執行

併發:多個程序看起來在被同時執行

同步:一個程序執行中調用另外一個 而且在調用的過程當中還要等待這個程序執行完畢在繼續執行

異步:一個程序執行中調用了另外一個 可是不等待這個任務完畢 就據需執行 start

阻塞:CPU不工做

非阻塞:CPU工做

第三十二章

Process模塊補充

主程序是在子程序執行完畢以後才結束

主程序回收子程資源

同時開啓多個子程序時,子程序的執行前後順序是操做系統分配的,由於在一個子程序執行IO操做時會有另外一個子程序進入被執行

import os
import time
from multiprocessing import Process

def son_process(i):
    print('start son',os.getpid(),i)
    time.sleep(1)
    print('end son')

if __name__ == '__main__':
    for i in range(5):
        Process(target=son_process,args=(i,)).start()
###
strat son 12256 1
strat son 13252 0
strat son 4088 2
strat son 15644 4
strat son 12292 3
end son
end son
end son
end son
end son

join:

會阻塞,等待對應的子程序執行結束後纔會繼續往下執行

from multiprocessing import Process
import time
def son_process(n):
    print('start',n)
    time.sleep(1)
    print('end',n)
if __name__ == '__main__':
    p_l = []
    for i in range(10):
        p = Process(target= son_process,args= (i,) )
        p.start()   
		### start至關於告訴操做系統要開啓一個子進程,而子進程的調度是由操做系統控制的
        p_l.append(p)
    for i in p_l:
        i.join()    
        ### join 會循環查看每一個子進程是否結束,未結束時會阻塞等待這個子進程結束,
        結束了就會查看下一個,都結束了纔會執行下面的
    print('全部任務結束')

守護進程:守護進程會隨着父進程的代碼結束而結束

from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('is son')
if __name__ == '__main__':
    p = Process(target= son)
    p.daemon = True     ###將當前子程序設置爲守護進程
    p.start()
    time.sleep(5)       ###主程序會在本身的代碼執行完以後殺死守護進程並回收

正常狀況下,父進程永遠會等着子進程結束

若是設置了守護進程,父進程的代碼結束以後,守護進程也跟着結束。

代碼結束和進程結束時兩回事:

沒設置守護進程時:

一、子進程的代碼和主進程的代碼本身執行本身的,互相之間不要緊
二、若是主進程的代碼先結束,主進程不結束,等待子進程代碼結束,回收子進程的資源,主進程才結束
三、若是子進程的代碼先結束,主進程邊回收子進程的資源邊執行本身的代碼,當代碼和資源都回收結束,主進程才結束

設置守護進程時:

一、子進程的代碼和主進程的代碼本身執行本身的,互相之間不要緊
二、一旦主進程的代碼先結束,主進程會先結束掉子進程,而後回收資源
面試題:求結果
from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('is son')

def son2():
    print('start son2')
    time.sleep(10)
    print('end son2')

if __name__ == '__main__':
    p = Process(target= son)
    p.daemon = True
    p.start()
    Process(target= son2).start()
    time.sleep(5)
#守護進程不會守護除了主進程代碼以外的其餘子進程
###
start son2
is son
is son
is son
is son
end son2

Process對象方法

start()###開啓子進程

join()###等待子進程執行完畢

terminate()###強行結束子進程 異步非阻塞操做

is_alive()###查看進程是否存活 返回布爾值

from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('in son')

if __name__ == '__main__':
    p = Process(target=son)
    p.start()
    time.sleep(5)

    p.terminate()   # 異步非阻塞操做
    print(p.is_alive())     ###True  由於是terminate是異步的 因此 可能會同時執行
    time.sleep(0.1)
    print(p.is_alive())     ###False
    print('我還能夠繼續作其餘的事情,主進程的代碼並不結束')

面向對象的方法開啓子進程:

from multiprocessing import Process
import os
class MyProcess(Process):
    def run(self):      ###重寫Process中的run方法
        print(os.getpid())
if __name__ == '__main__':
    print('主:',os.getpid())
    MyProcess().start()



傳參數:
from multiprocessing import Process
import os

class MyProcess(Process):
    
    def __init__(self,arg1,arg2):
        super().__init__()  ###執行父類中的init,必需要執行父類的init
        self.a1 = arg1      ###執行重寫的init,能夠加可是不能減
        self.a2 = arg2
        
    def run(self):      ###重寫Process中的run方法
        print(os.getpid(),self.a1,self.a2)
        
if __name__ == '__main__':
    
    print('主:',os.getpid())
    MyProcess(1,2).start() ###開啓子進程

進程之間的數據是否隔離:

n = 0
def son(i):
    global n
    n += 1
    print(n,i)

if __name__ == '__main__':
    p_l = []
    for i in range(5):
        p = Process(target= son,args=(i,))
        p.start()
        p_l.append(p)
    print(n)
###
0
1 0
1 2
1 3
1 1
1 4

數據安全(鎖):用來保證數據安全

若是多個進程同時對一個文件進行操做會出現什麼問題:

讀數據:能夠同時讀

寫數據:不能同時寫

不上鎖時多個進程同時操做同一個文件,可能同時讀到這個文件,而後有的是讀到上一個修改後的文件,也有可能會在另外一個文件正在寫的過程當中讀到一個空的文件,由於他們讀文件的時候不是原子性的,因此不安全,上鎖後每一個進程走到上鎖的代碼時是原子性的,遵循先進先出原則,只有一個進程在操做文件,因此保證了數據的安全

from multiprocessing import Process,Lock
import os
def charge(lock):
    lock.acquire()      ###上鎖
    with open('lt','r')as f:
        content = f.read()
    num = int(content)
    num += 1
    for i in range(10000):i += 1
    with open('lt','w')as f:
        f.write(str(num))
    lock.release()      ###解鎖
if __name__ == '__main__':
    lock = Lock()		###實例化一個對象(鎖)
    for i in range(10):
        Process(target= charge,args=(lock,)).start()

進程之間的數據共享(進程之間的通訊)(IPC)

全稱:Inter Process Communication

from multiprocessing import Process,Queue
def son(q):
    print(q.get(123))      ###從隊列中取出

if __name__ == '__main__':
    q = Queue()       	 ###實例化一個隊列
    p = Process(target= son,args=(q,))
    p.start()
    q.put(123)      	###發送到隊列中

在進程之間維護數據之間的安全 -- 進程安全

隊列是進程安全的(進程隊列保證了進程的數據安全)

隊列都是先進先出

隊列是基於文件 + 鎖實現的

隊列一共提供兩個方法:get put

get是一個同步阻塞方法,會阻塞直到數據的到來

get_notwait()同步非阻塞方法

put是一個同步阻塞方法,會阻塞直到隊列不滿

put_notwait()同步非阻塞方法

q.qsize() 查看當前隊列有多少值 可是不許 由於程序之間是異步的

empty() 查看隊列是否爲空的

full() 查看隊列是否爲滿

from multiprocessing import Process,Queue
import queue
q = Queue(2)
try:
    for i in range(4):
        q.put_nowait(i)  # put_nowait 同步非阻塞方法
except queue.Full:
    print(i)

q2 = Queue(2)
try:
    print(q2.get_nowait())
except queue.Empty:pass

q = Queue(5)
ret = q.qsize()  # 查看當前隊列有多少值
print(q.empty())
print(q.full())

隊列Queue = 管道Pipe + 鎖

Pipe 基於文件實現的(socket + pickle) = 數據不安全

Queue基於文件(socket + pickle) + 鎖 (lock)實現的 = 數據安全

基於pipe + 鎖(lock)實現的

IPC:

內置的模塊實現的機制 :隊列\管道
第三方工具:redis rabbitMQ memcache

第三十三章

  • 閹割版單例模塊html

    class Foo:
        instance = None
    
        def __init__(self, name):
            self.name = name
        def __new__(cls, *args, **kwargs):
            # 返回空對象
            if cls.instance:
                return cls.instance
            cls.instance = object.__new__(cls)
            return cls.instance
    
    obj1 = Foo('小妹')
    obj2 = Foo('傻子')
    
    print(obj1,obj2)
  • 爬蟲下載圖片python

    1. 安裝第三方模塊
    	pip3 install requests
    
    2. 使用
    	import requests
    	url = 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg'
    
    	# python僞造瀏覽器向地址發送請求
    	rep = requests.get(url)
    
    	# 請求返回回來的字節
    	# print(rep.content)
    
    	with open('xxxxxx.jpg',mode='wb') as f:
    		f.write(rep.content)
    import requests
    
    url_list = [
    'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
    'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
    'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
    ]
    
    for url in url_list:
    	ret = requests.get(url)
    	file_name = url.rsplit('/',maxsplit=1)[-1]
    	with open(file_name,mode='wb') as f:
    		# 下載小文件
    		# f.write(ret.content)
    
    		# 下載大文件
    		for chunk in ret.iter_content():
    			f.write(chunk)
    • 注意:以上代碼是基本於串行來實現
  • 線程的概念git

    形象的關係
    工廠--應用程序
    車間--進程
    工人--線程
  • 進程和線程的區別?github

    進程師計算機資源分配的最小單位
    線程師計算機中能夠被cpu調度的最小單位
    一個進程中能夠有多個線程,同一個進程中的線程能夠共享此進程中的資源,一個進程中至少有一個線程(一個應用程序中至少有一個進程)
    
    在python中由於有GIL鎖,
    
    默認進程之間沒法進行資源共享,若是進程想要通信能夠基於:文件/網絡/Queue
  • 多線程的應用web

    • 快速應用面試

      import threading
      
      def task(arg):
      	pass
      # 實例化一個線程對象
      t = threading.Thread(target=task,args=('xxx',))
      # 將線程提交給cpu
      t.start()
      import threading
      def task(arg):
      	ret = requests.get(arg)
      	file_name = arg.rsplit('/', maxsplit=1)[-1]
      	with open(file_name, mode='wb') as f:
      		f.write(ret.content)
      		
      for url in url_list:
      	# 實例化一個線程對象
      	t = threading.Thread(target=task,args=(url,))
      	# 將線程提交給cpu
      	t.start()
  • 常見方法redis

    • t.start(),將線程提交給cpu,由cpu來進行調度算法

    • t.join(),等待編程

      import threading
      
      示例1
      loop = 1000
      number = 0
      
      def _add(count):
      	flobal number
      	for i in range(count):
      	number +=1
      	
      t = threading.Thread(target = _add,args=(loop,))
      t.start()
      
      t.join()
      print(number)
      import threading
      
      示例2
      loop = 1000
      number = 0
      
      def _add(count):
      	global number
      	for in in range(count):
      		number +=1
      
      def _sub(count):
      	global number
      	for i in range(count):
      		number -= 1
      		
      t1 = threading.Thread(target=_add,args(loop,))
      t2 = threading.Thread(target=_add,args(loop,))
      t1.start()
      t2.atart()
      
      print(number)
      import thrading
      
      示例3
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      def _sub(count):
          global number
          for i in range(count):
              number -= 1
      
      t1 = threading.Thread(target=_add,args=(loop,))
      t2 = threading.Thread(target=_sub,args=(loop,))
      t1.start()
      t2.start()
      t1.join() # t1線程執行完畢,才繼續日後走
      t2.join() # t2線程執行完畢,才繼續日後走
      
      print(number)
      import thrading
      
      示例4
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      def _sub(count):
          global number
          for i in range(count):
              number -= 1
      
      t1 = threading.Thread(target=_add,args=(loop,))
      t2 = threading.Thread(target=_sub,args=(loop,))
      t1.start()
      t1.join() # t1線程執行完畢,才繼續日後走
      t2.start()
      t2.join() # t2線程執行完畢,才繼續日後走
      
      print(number)
  • t.setDaemon(),設置成爲守護線程小程序

    • 守護線程 : 守護整個主線程的
    • 子線程先結束
    • 主線程結束
    • 主進程結束(意味着主進程就結束了,一旦主進程結束,全部的屬於這個進程的資源都被回收了)
    • 主進程結束了 若是還存在守護線程,那麼守護線程會隨着進程的結束被回收
    import threading
    import time
    
    def task(arg):
    	time.sleep(5)
    	print('小妹')
    
    t = threading.Thread(target=task,args=(11,))
    t.setDaemon(True)
    t.start()
    
    print('小妹')
  • 線程名稱的設置和獲取

    import threading
    
    def task(arg):
    	#獲取當前執行此代碼的線程
    	name = threading.current+thread().getName()
    	print(name)
    	
    for i in range(10):
    	t = threading.Thread(target=task,args=(11,))
    	t.setName('小妹-%s' %i)
    	t.start()
  • run(),自定義線程時,cpu調度執行的方法

    class XIAOMEI(threading.Thread):
    	def run(self):
    		print(‘執行此線程',self._args)
    		
    	obj = XIAOMEI(args=(100,))
    	obj.start()
  • 練習題:基於socket 和多線程實現相似於socketserver模塊的功能

    import socket
    import threading
    
    def task(connect,address):
    	pass
    
    server = socket.socket()
    serber.bind('127.0.0.1',9000)
    server.listen(5)
    Whlie True:
    	conn,addr = server.accept()
    	#處理用戶請求
    	t= threading.thread(target=task,args=(conn,addr,))
    	t.start()
  • 線程安全

    • 多線程同時去操做一個‘東西’,不要存在數據混亂
    • 線程安全:logging模塊/列表/Queue
    • 線程不安全:本身作文件操做/同時修改一個數字
    • 使用鎖來保證數據安全,來了多個線程,使用鎖讓他們排隊,逐一執行
    • 但凡是待着判斷 判斷要作xxx,都要加鎖
      • if while += -= *= /= 都須要加鎖

    Lock 互斥鎖

    import threading
    import time
    
    def task():
    	global num 
    	#申請鎖
    	lock.acquire()
    	num += 1
    	time.sleep(0.2)
    	print(num)
    	#釋放鎖
    	lock.release()
    	
    	with lock:
    		num += 1
    		time.sleep(0.2)
    		print(num)
    for i in range(10):
    	t = threading.Thread(target=task)
    	t.start()

    RLock,遞歸鎖支持上屢次鎖

    import threading
    import time
    
    num = 0
    #線程鎖
    locs = threading.RLock()
    
    def task():
    	golbal num
    	#申請鎖
    	lock.acquire()
    	num +=1
    	lock.acquire()
    	time.sleep(0.2)
    	print(num)
    	#釋放鎖
    	lock.release()
    	lock.release()
    
    for i in range(10):
    	t=threading.Thread(target=task)
    	t.start()
  • 死鎖

    • Lock嵌套鎖
    • 多個鎖交叉使用(儘可能避免)
    import threading
    import time
    lock1 = threading.RLock()
    lock2 = threading.RLock()
    def fenglin():
        lock1.acquire()
        time.sleep(1)
        lock2.acquire()
    def rimo():
        lock2.acquire()
        time.sleep(1)
        lock1.acquire()
    t1 = threading.Thread(target=fenglin)
    t2 = threading.Thread(target=rimo)
    t1.start()
    t2.start()
  • 基於線程鎖完成一個單例模式

    import threading
    import time
    class XIAOMEI():
    	instance = None
    	lock = threading.RLock()
    	
    	def __init__(self,name):
    		self.name = name
    		
    	def __new__(cls,*args,**kwargs):
    		if cls.instance:
    			return cls.instance
    		with cls.lock:
    			if cls.instance
    				return cls.instance
    			time.sleep(0.1)
    			cls.instance=object.__new__(cls)
    		return cls.instance
    
    def task():
    	obj = Singleton('x')
    	print(obj)
    	
    for i in range(10):
    	t = threading.Thread(target=task)
    	t.start()
    
    data = Singleton('xiaomei')
    print(data)
  • GIL全局解釋鎖

    同一時刻保證一個進程中只有一個線程能夠被cpu調度,因此在使用python開發時要注意:
    	計算密集型,用多進程
    	IO密集型,用多線程
    GIL在列表和字典中起到了線程安全的做用
    • python中若是建立多線程沒法應用計算機的多核優點

第三十四章

線程池

Python2中沒有提供線程池,python3以後解釋器才提供了線程池.

保證程序中最多能夠建立的線程個數,防止無節制的建立線程,致使性能下降.

import time
from concurrent.futures import ThreadPoolExecutor
def task(n1, n2):
    time.sleep(2)
    print('任務')
# 建立線程池
pool = ThreadPoolExecutor(10)
for i in range(100):
    pool.submit(task, i, 1)
print('END')
# 等線程池中的任務執行完畢以後,再繼續往下走
pool.shutdown(True)
print('其餘操做,依賴線程池執行的結果')

函數的返回值存在submit返回的對象裏面,用result()方法查看
import time
from concurrent.futures import ThreadPoolExecutor

def task(arg):
    time.sleep(2)
    print('任務')
    return 666
# 建立線程池
pool = ThreadPoolExecutor(10)
ret = pool.map(task,range(1,20))
print('END',ret)
pool.shutdown(True)
for item in ret:
    print(item)

函數的返回值存在map的返回值中

回調函數

import requests
from concurrent.futures import ThreadPoolExecutor

def get_url(name,url):
    ret = requests.get(url)
    return name,ret.content

def dump(res):
    with open(res.result()[0],mode='wb') as f:
        f.write(res.result()[1])

tp = ThreadPoolExecutor(4)
dic = {
    'baidu':'http://www.baidu.com',
    'taobao':'http://www.taobao.com',
    'jd':'http://www.jd.com',
    'sina':'http://www.sina.com',
    'sogou':'http://www.sogou.com',
    'cnblog':'https://www.cnblogs.com/Eva-J/articles/9676220.html',
}
for k in dic:
    ret = tp.submit(get_url,k,dic[k])
    ret.add_done_callback(dump)
###requests會發生阻塞,等待全部線程都回來,使用回調函數以後哪一個線程先回來就先執行哪一個

進程池

import time
from concurrent.futures import ProcessPoolExecutor
def task(n1, n2):
    time.sleep(2)
    print('任務')

if __name__ == '__main__':

    # 建立線程池
    pool = ProcessPoolExecutor(10)
    for i in range(20):
        pool.submit(task, i, 1)
    print('END')

協程

from greenlet import greenlet


def test1():
    print('i1')
    gr2.switch()
    print('i3')
    gr2.switch()


def test2():
    print('i2')
    gr1.switch()
    print('i4')


gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
###單單是協程沒用 必需要加上遇到IO自動切換

協程+IO切換

from gevent import monkey
monkey.patch_all()				###必須寫在前面

import gevent
import time


def eat():
    print('eat food 1')
    time.sleep(3)
    print('eat food 2')


def play():
    print('play 1')
    time.sleep(3)
    print('play 2')


g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1, g2])

協程+IO切換+爬蟲示例

from gevent import monkey
monkey.patch_all()


import gevent
import requests

def f1(url):
    print('GET: %s' % url)
    data = requests.get(url)
    print('%d bytes received from %s.' % (len(data.content), url))

def f2(url):
    print('GET: %s' % url)
    data = requests.get(url)
    print('%d bytes received from %s.' % (len(data.content), url))

def f3(url):
    print('GET: %s' % url)
    data = requests.get(url)
    print('%d bytes received from %s.' % (len(data.content), url))

gevent.joinall([
        gevent.spawn(f1, 'https://www.python.org/'),
        gevent.spawn(f2, 'https://www.yahoo.com/'),
        gevent.spawn(f3, 'https://github.com/'),
])

進程、線程、協程的區別:

(重中之重之重之重之重之重之重之重之重之重)

相同性:
	進程、協程、線程的區別:三個均可以提升併發
概念和關係:
	進程是計算機中分配資源的最小單位,進程之間數據不共享,進程開啓和銷燬的開銷大,由操做系統調度,能夠利用多個 cpu。
	線程是計算機中CPU調度的最小單位,線程之間資源共享,能夠利用多核(可是CPython中有GIL鎖),開啓和銷燬的開銷小,由操做系統負責調度。
	協程又稱爲「微線程」,是基於代碼人爲創造的,資源開銷小,協程可以識別的IO操做:網絡操做,sleep,一個進程中能夠有多個線程,一個線程能夠建立多個協程
	協程和cpython解釋器下的線程有啥區別 :資源開銷小 可以把單線程的效率提升 協程可以識別的io操做不如線程多
使用場景:
    計算密集型,多進程;
    I0密集型,多線程/協程+ I0切換
    單純的協程是沒辦法提升併發,只是代碼之間的來回切換,加上I0自動切換才
    有意義,有I0操做用協程

隊列

from queue import Queue
q = Queue()
q.put('123')
q.put(456)

v1 = q.get()
v2 = q.get()
print(v1,v2)


# 默認阻塞
v1 = q.get()
print(v1)

棧:LifoQueue 後進先出棧

from queue import LifoQueue
lq = LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())

優先級隊列:PriorityQueue 優先級隊列

from queue import PriorityQueue
pq = PriorityQueue()
pq.put((10,'alex'))
pq.put((5,'egon'))
pq.put((15,'yuan'))
print(pq.get())
print(pq.get())
print(pq.get())

發郵件

1. 申請126或163郵箱

2. 開啓服務+受權碼

3. 經過代碼發送
"""

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr

# 寫郵件的內容
msg = MIMEText('老闆,我想演男一號,你想怎麼着都行。', 'plain', 'utf-8')
msg['From'] = formataddr(["炮手", 'zh309603@163.com'])
msg['To'] = formataddr(["老闆", '424662508@qq.com'])
msg['Subject'] = "情愛的導演"

server = smtplib.SMTP_SSL("smtp.163.com", 465)
server.login("zh309603", "zhzzhz123") # 受權碼
server.sendmail('zh309603@163.com', ['424662508@qq.com', ], msg.as_string())
server.quit()

生產者消費者模型

就是把大量的數據放到隊列中
而後有大量服務器從隊列中取出整理這些數據
import threading
from queue import Queue
import time

q = Queue()

def send(to,subject,text):
    import smtplib
    from email.mime.text import MIMEText
    from email.utils import formataddr

    # 寫郵件的內容
    msg = MIMEText(text, 'plain', 'utf-8')
    msg['From'] = formataddr(["炮手", 'zh309603@163.com'])
    msg['To'] = formataddr(["老闆", to])
    msg['Subject'] = subject

    server = smtplib.SMTP_SSL("smtp.163.com", 465)
    server.login("zh309603", "zhzzhz123")  # 受權碼
    server.sendmail('zh309603@163.com', [to, ], msg.as_string())
    server.quit()

def producer(i):
    """
    生產者
    :return:
    """
    print('生產者往隊列中放了10個任務',i)
    info = {'to':'424662508@qq.com', 'text':'你好','subject':'好友請求'}
    q.put(info)


def consumer():
    """
    消費者
    :return:
    """
    while True:
        print('消費者去隊列中取了任務')
        info = q.get()
        print(info)
        send(info['to'],info['subject'],info['text'])

for i in range(10):
    t = threading.Thread(target=producer,args=(i,))
    t.start()

for j in range(5):
    t = threading.Thread(target=consumer)
    t.start()
相關文章
相關標籤/搜索