前言:本文是學習網易微專業的《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_data
的data
屬性,只能被自己的線程訪問與修改,即使所有的線程中都包含同名的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.如果變量需要多個線程共享共同操作,可以加鎖