Python服務器運維筆記:第二章Linux - 1.2.10 多線程

前言:本文是學習網易微專業的《python全棧工程師》 中的《服務器運維開發工程師》專題的課程筆記,歡迎學習交流。同時感謝老師們的精彩傳授!

一、課程目標

  • 多線程概念
  • 多線程實現
  • 多線程server socket

二、詳情解讀

2.1.什麼是多線程

什麼是進程?
我們每運行一個程序,就是創建一個進程。每個進程運行的時候會向系統申請資源:內存空間,cpu時間,進程之間的資源相互獨立

什麼是線程?
當一個進程運行的時候,它內部就會存在一個主線程。線程就是程序內部需要完成的任務。在只有一個線程的情況,這些任務需要按照次序,逐個完成。所有線程共享進程資源。
在這裏插入圖片描述
單線程的問題:
由於一個線程的情況,這些任務需要按照次序,逐個完成,所以當其中一個任務耗時比較長,那麼其他任務就需要等待,這樣就浪費了等待時間
在這裏插入圖片描述
多線程:
將進程中的多個任務獨立開來運行,就是多線程。

第一次列表頁面爬取後,提取內容列表任務變可以在第二次列表頁面爬取期間完成,這樣就會提高運行效率。

在這裏插入圖片描述

2.1.1.threading模塊

多線程不代表一定同時執行。 多線程可以串行,也可以並行。
在這裏插入圖片描述

2.2.threading模塊
from threading import Threading, active_count, current_thread, local
t = Thread(target=func, args=(param1, param2), kwargs={})
t.start() # 開始運行該線程
t.join()  # 其他線程必須等待此線程完成,才能運行

說明:
1).Thread 表示線程對象,通過該對象創建線程
2).t爲線程實例
3).active_count - 函數,返回當前活動線程數
4).current_thread - 函數,當前該線程
5).local - 函數,線程局部變量
6).Lock - 鎖對象

示例代碼如下:

from threading import Thread
from random import randint
from time import sleep,time

def myprint(tid):
    sleep(randint(0,3))
    print("threading:",tid)
def main():
    for i in range(10):
        t = Thread(target=myprint,args=(i,))
        t.start()
        # t.join()       

if __name__ == "__main__":
    main()
    print("main stop....")

運行結果:
在這裏插入圖片描述
上面的程序,如果在t.start()後面添加t.join(),則每個線程都會等待前面的線程結束纔會執行,結果如下圖:
在這裏插入圖片描述

接着,測試多線程與非多線程的時間差別:

線程按照順序執行:

# -*- coding=utf-8 -*-
from threading import Thread
from random import randint
from time import sleep, time

def myprint(tid):
    sleep(randint(0, 3))
    print('threading: ', tid)

def main():
    for i in range(10):
        t = Thread(target=myprint, args=(i, ))
        t.start()
        t.join()

if __name__ == '__main__':
    start_time = time()
    main()
    print('main stop...')
    end_time = time()
    print(end_time - start_time)

在這裏插入圖片描述
多線程的運行:

# -*- coding=utf-8 -*-
from threading import Thread
from random import randint
from time import sleep, time

def myprint(tid):
    sleep(randint(0, 3))
    print('threading: ', tid)

threads = []
def main():
    for i in range(10):
        # print('create', i)
        t = Thread(target=myprint, args=(i, ))
        t.start()
        threads.append(t)

if __name__ == '__main__':
    start_time = time()
    main()
    print('main stop...')
    # 檢查每一個線程是否執行完畢
    for i in threads:
        i.join()
    end_time = time()
    print(end_time - start_time)

在這裏插入圖片描述
通過上面的測試,可以看出,多線程可以大大提高程序的執行效率。

消費者與生產者示例代碼:

# -*- coding: utf-8 -*-

from threading import Thread, current_thread
from time import sleep
from random import randint
from queue import Queue

q = Queue(20)
def put_data():
    while True:
        n = randint(0,3)
        sleep(n)
        q.put(n)
        print(current_thread(), "向隊列寫入>>:",n)

def get_data():
    while True:
        data = q.get()
        print(current_thread(), "從隊列讀取>>", data)

def main():

    t1 = Thread(target=put_data)
    t2 = Thread(target=get_data)
    t1.start()
    t2.start()

if __name__ == "__main__":
    main()

local線程局部變量
線程局部對象可以被所有線程訪問,比如local_data,可以被所有線程訪問,但是線程局部對象的屬性只能被定義該屬性的線程自身訪問,比如local_datadata屬性,只能被自己的線程訪問與修改,即使所有的線程中都包含同名的local_data.data屬性,因此這個data實際上只是線程內部的局部變量,藉助線性局部對象可以簡化代碼邏輯,保持線程間變量隔離。

示例代碼:

# -*- coding=utf-8 -*-
from threading import Thread, current_thread, local
from random import randint
from time import  sleep

local_data = local()
data = 0
def set_data():

    local_data.data = randint(0,100)
    print(current_thread().name+"::data="+str(local_data.data))
    sleep(randint(0,3))
    print(current_thread().name+":: after_sleep::data="+str(local_data.data))
    # local_data.data = data
    out()

def out():
    print(local_data.data)

def main():
    for i in range(3):
        t1 = Thread(target=set_data)
        t1.start()

if __name__ == "__main__":
    main()

執行結果:
在這裏插入圖片描述

2.3.線程鎖

所有線程可以共享一個進程內的資源,如果同時操作某一個對象,就可能發生混亂,這時候可以通過加鎖解決。

線程鎖通過Lock對象實例實現

l = Lock()

包含兩個實例方法:

l.acquire()  # 加鎖
l.release()  # 解鎖

示例代碼:

# -*- coding: utf-8 -*-

from threading import Thread, Lock, current_thread
from time import sleep
from random import randint

lock = Lock()
number = [0]

def loop(i):
    lock.acquire()
    try:
        if i > number[-1]:
            sleep(randint(0, 5))
            number.append(i)
    except:
        pass
    finally:
        print("thread %s ended." % current_thread().name, number, "\r\n")
        lock.release()
        pass


if __name__ == "__main__":

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

執行結果:
在這裏插入圖片描述

2.4.多線程socket_server實現
2.4.1.單線程socket_server問題

在這裏插入圖片描述
在這裏插入圖片描述
代碼演示:

服務端代碼:

from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread

s_server = socket(AF_INET, SOCK_STREAM)
s_server.bind(("127.0.0.1", 8000))
s_server.listen()

def chat(client):
    while True:
        try:
            message = client.recv(1024)
        except:
            break
        else:
            print(message.decode("utf-8"))
            # 空字符串跳過
            if not message:
                continue
            if message == "quit":
                print("close")
                client.close()
                break
            else:
                print("send")
                client.send(b"\r\nrecived...")    


while True:
    print("wait......")
    s_client, addr = s_server.accept()
    t = Thread(target=chat, args=(s_client,))
    t.start()

客戶端代碼:

# -*- coding: utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread

s_client = socket(AF_INET, SOCK_STREAM)
s_client.connect(("127.0.0.1", 8000))


def chat():
    print("wait...")
    while True:
        message = input("請輸入消息:")

        if not message:
            continue
        s_client.send(bytes(message, encoding="utf-8"))
        
        if message == "quit":
            break
    s_client.close()
    print("close()")

def get_message():
    while True:
        try:
            message = s_client.recv(1024)
            print(message.decode())
        except Exception as e:
            print(e)
            break
        finally:
            pass

t = Thread(target=chat)
t.start()
t = Thread(target=get_message)
t.start()

三、課程小結

  • 01 threading模塊
  • 02 服務器端
  • 03 客戶端

線程開發要注意變量之間的操作,防止變量互相干擾。
方法有兩種:
1.如果變量只是對線程內部有效,可以使用線程局部變量。
2.如果變量需要多個線程共享共同操作,可以加鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章