แนวคิด Multithreading
Multithreading เป็นแนวคิดหลักของภาษาโปรแกรมมิ่งสมัยใหม่เกือบทุกภาษา โดยเฉพาะ Python เนื่องจากการนำเธรดไปปฏิบัติที่เรียบง่าย
เธรด (Thread) คือ โปรแกรมย่อยภายในโปรแกรมที่สามารถรันได้อย่างอิสระจากส่วนอื่นของโค้ด เธรดทำงานในบริบทเดียวกัน แชร์ทรัพยากรที่รันได้ของโปรแกรม เช่น หน่วยความจำ
เมื่อในกระบวนการเดียว เรากำลังรันเธรดหลายตัวพร้อมกัน เรียกว่า multithreading
โมดูล Python Multithreading สำหรับการนำเธรดไปปฏิบัติ
เพื่อนำเธรดไปปฏิบัติในโปรแกรม Python มีสองโมดูล:
- thread (สำหรับ Python 2.x) หรือ _thread (สำหรับ Python 3.x) module
- threading module
โมดูล thread สร้างเธรดเป็นฟังก์ชัน ในขณะที่โมดูล threading ให้วิธีการเชิงออบเจกต์ในการสร้างเธรด
ไวยากรณ์
_thread.start_new_thread(func, args[, kwargs])
ข้างต้นเริ่มเธรดใหม่และคืนค่าตัวระบุของมัน อาร์กิวเมนต์แรกคือฟังก์ชัน func ซึ่งเธรดจะรันด้วยอาร์กิวเมนต์ที่สองที่มีทูเพิลพร้อมรายการอาร์กิวเมนต์ตำแหน่ง อาร์กิวเมนต์ kwargs ที่เป็นตัวเลือกระบุดิกชันนารีของอาร์กิวเมนต์คีย์เวิร์ด เมื่อฟังก์ชันคืนค่า เธรดจะจบอย่างเงียบๆ
ในที่นี้ เราจะเห็นตัวอย่างพื้นฐานของแอปพลิเคชัน client-server โดย clients เปิดการเชื่อมต่อ socket และส่งคำขอไปยัง server server ตอบกลับ
เมื่อรันโดยไม่มีอาร์กิวเมนต์ โปรแกรมนี้เริ่มต้นด้วย TCP socket server ที่ฟังการเชื่อมต่อที่ 127.0.0.1 บนพอร์ต 8000
client_thread1.py
import socket
import sys
def main():
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "127.0.0.1"
port = 8000
try:
soc.connect((host, port))
except:
print("Connection Error")
sys.exit()
print("Please enter 'quit' to exit")
message = input(" -> ")
while message != 'quit':
soc.sendall(message.encode("utf8"))
if soc.recv(5120).decode("utf8") == "-":
pass # null operation
message = input(" -> ")
soc.send(b'--quit--')
if __name__ == "__main__":
main()
ในขณะที่โปรแกรม server คือ:
server_thread1.py
import socket
import sys
import traceback
from threading import Thread
def main():
start_server()
def start_server():
host = "127.0.0.1"
port = 8000 # arbitrary non-privileged port
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print("Socket created")
try:
soc.bind((host, port))
except:
print("Bind failed. Error : " + str(sys.exc_info()))
sys.exit()
soc.listen(6) # queue up to 6 requests
print("Socket now listening")
# infinite loop - do not reset for every requests
while True:
connection, address = soc.accept()
ip, port = str(address[0]), str(address[1])
print("Connected with " + ip + ":" + port)
try:
Thread(target=client_thread, args=(connection, ip, port)).start()
except:
print("Thread did not start.")
traceback.print_exc()
soc.close()
def clientThread(connection, ip, port, max_buffer_size=5120):
is_active = True
while is_active:
client_input = receive_input(connection, max_buffer_size)
if "--QUIT--" in client_input:
print("Client is requesting to quit")
connection.close()
print("Connection " + ip + ":" + port + " closed")
is_active = False
else:
print("Processed result: {}".format(client_input))
connection.sendall("-".encode("utf8"))
def receive_input(connection, max_buffer_size):
client_input = connection.recv(max_buffer_size)
client_input_size = sys.getsizeof(client_input)
if client_input_size > max_buffer_size:
print("The input size is greater than expected {}".format(client_input_size))
decoded_input = client_input.decode("utf8").rstrip()
result = process_input(decoded_input)
return result
def process_input(input_str):
print("Processing the input received from client")
return "Hello " + str(input_str).upper()
if __name__ == "__main__":
main()
เมื่อรันสคริปต์ข้างต้น ให้รัน server_thread1.py ในเทอร์มินัล:
python server_thread1.py Socket created Socket now listening
เราจะดูหน้าต่าง server และเข้าใจโฟลว์ ตอนนี้เปิดเทอร์มินัล client หลายตัว รัน client thread
python client_thread1.py Enter 'quit' to exit -> Zack ->
ในเทอร์มินัลอื่น รันโปรแกรม client อีกตัว และดูหน้าต่าง server ด้วย:
python client_thread1.py Enter 'quit' to exit -> Python -> quit
เทอร์มินัลอีกตัว รัน client thread:
python client_thread1.py Enter 'quit' to exit -> world! -> Anothny ->
และเราจะเห็นหน้าต่าง server แสดงผลลัพธ์ประมาณนี้:
Socket created Socket now listening Connected with 127.0.0.1:50275 Processing the input received from client Processed result: Hello ZACK Connected with 127.0.0.1:50282 Processing the input received from client Processed result: Hello PYTHON Processing the input received from client Client is requesting to quit Connection 127.0.0.1:50282 closed Connected with 127.0.0.1:50285 Processing the input received from client Processed result: Hello WORLD! Processing the input received from client Processed result: Hello ANOTHNY
ดังนั้น เธรดจึงให้เทคนิคที่พบบ่อยที่สุดในการจัดการการเชื่อมต่อ socket หลายรายการและ clients