1、创建线程的另外一种方式
1.1、之前创建线程方式:
import threading
def task_1(num):
pass
t = threading.Thread(target = 函数名,args = (11,),kwargs = {"xx":yyy}
t.start() # 真正的创建线程
threading.enumrate() #查看数量
1.2、另一种方式--封装性更好的一种创建线程的方式--封装类
使用类的方式创建线程
import threading
class UDP(threading.Thread):
def run(self):
pass
u_t = UDP()
# u_t.run()
u_t.start()
1.3、使用类创建子线程demo
import threading
import time
class Task(threading.Thread):
def run(self):
while True:
print("11111")
time.sleep(1)
t = Task()
t.start()
while True:
print("main")
time.sleep(1)
运行结果:
11111和main是一起出来的
11111
main
main
11111
小结:
- 可以自己定义一个类,但是这个类要继承Thread
- 一定要实现run方法,即要定义一个run方法,并且实现线程需要执行的代码。
- 当调用自己编写的类创建出来的实例对象的run方法时,会创建新的线程,并且线程会自动调用run方法。
- 如果除了run方法之外,还定义了其他的方法,例如xx,那么这些方法需要在run方法中自己去调用,线程自己不会调用。例如self.xx()
- 私有属性在子类里面不能被调用
2、并发TCP服务器
并发--同一时刻为多个项目服务,一起服务叫并发
import socket
import threading
class HandleData(threading.Thread):
def __init__(self, client_socket):
super().__init__()
self.client_socket = client_socket
def run(self):
# 接收/发送数据
while True:
recv_content = self.client_socket.recv(1024)
if len(recv_content) != 0:
print(recv_content)
self.client_socket.send(recv_content)
else:
self.client_socket.close()
break
def __del__(self):
self.client_socket.close()
class TCPServer(threading.Thread):
def __init__(self, port):
# 调用父类的初始化方法
# threading.Thread.__init__(self)
super().__init__()
# 创建套接字
self.server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地信息
self.server_s.bind(("", port))
# 将套接字由默认的主动链接模式改为被动模式(监听模块)
self.server_s.listen(128)
def run(self):
# 等待客户端进行链接
while True:
new_s, client_info = self.server_s.accept()
print(client_info)
# t = HandleData(new_s)
# t.start()
HandleData(new_s).start()
def __del__(self):
# 关闭套接字
self.server_s.close()
def main():
tcp_server = TCPServer(7788) # 7788表示TCP要绑定的端口
tcp_server.start()
if __name__ == '__main__':
main()
小结:
- 可以通过定义一个新的类,继承threading.Thread的方式创建线程
- 创建这个线程对象的时候,可以像使用普通的类一样,给它的
__init__
方法创建参数 - 在
__init__
方法中通过super().__init__()
调用被覆盖的父类方法,能够保证父类需要进行的准备工作能够正常执行 - 可以在
__del__
方法中调用close()
关闭套接字
3、队列(Queue)
3.1、为什么使用队列?
多任务线程很多时候需要相互配合才能完成一件事情,例如,一个线程专门用来接收数据,另外一个线程专门用来存储刚刚接收的数据,如果需要将以上2个线程相互配合那么理论上来说,效率会很高。但是线程中的变量各自是各自的,为了能够让多个线程之间共享某些数据,就可以使用队列来实现数据共享。
3.2、什么是队列?
程序中一种特殊的存储数据的方式,可以实现先存入数据,先出去。
3.3、如何使用?
3.3.1、队列Queue--->先进先出的
往队列里放东西用---->put
import queue
q = queue.Queue()
q.put("11") # 存入字符串
q.put(22) # 存入整数
q.put({'num': 100}) # 存入字典
print(q.get()) # 11
print(q.get()) # 22
print(q.get()) # {'num': 100}
小结:
- 先进先出(FIFO)
- 可以存放任意类型数据,例如整数、字符串、字典
- 存入使用put(),取出使用get()(如果当前队列中没有数据、此时会堵塞)
3.3.2、堆栈Queue----->后进的先出
import queue
q = queue.LifoQueue()
q.put('11') # 存入字符串
q.put(22) # 存入整数
q.put({'num': 100}) # 存入字典
print(q.get()) # {'num': 100}
print(q.get()) # 22
print(q.get()) # 11
小结:
- LIFO-->后进先出
- 可以存放任意数据类型
- 存入使用put(),取出使用get()(如果当前队列中没有数据、此时会堵塞)
3.3.3、优先级Queue
放的时候要放元组,第一个元素表示优先级,数字越小,优先级越高;第二个元素表示要存放的数据。
import queue
q = queue.PriorityQueue()
q.put((10, 'Q'))
q.put((30, 'Z'))
q.put((20, 'A'))
print(q.get()) # (10, 'Q')
print(q.get()) # (20, 'A')
print(q.get()) # (30, 'Z')
小结:
- 存放的数据是元组类型,第1个元素表示优先级,第2个元素表示存储的数据
- 优先级数字越小优先级越高
- 数据优先级高的优先被取出
- 用于VIP用户数据优先被取出场景,因为上面两种都要挨个取出
4、带有聊天记录的UDP聊天程序
创建三个线程,一个队列
import socket
import threading
import queue
class SendMsg(threading.Thread):
def __init__(self, udp_socket, queue):
super().__init__()
self.udp_socket = udp_socket
self. queue = queue
def run(self):
"""获取键盘数据,并将其发送给对方"""
while True:
print("1: 发送数据")
print("2: 退出程序")
op = input("请输入操作序号:")
if op == "1":
# 1. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 2. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
while True:
# 3. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
if msg:
# 4. 发送数据
self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
info = "<<<(%s, %d):%s\n" % (dest_ip, dest_port, msg)
self.queue.put(info)
else:
# 要是没有输入内容则认为是要重新输入ip、port
break
elif op == "2":
break
def __del__(self):
self.udp_socket.close()
class RecvMsg(threading.Thread):
def __init__(self, udp_socket, queue):
super().__init__()
self.udp_socket = udp_socket
self.queue = queue
def run(self):
"""接收数据并显示"""
while True:
try:
# 1. 接收数据
recv_msg = self.udp_socket.recvfrom(1024)
except:
break
else:
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
info = ">>>%s:%s\n" % (str(recv_ip), recv_msg)
print(info)
self.queue.put(info)
def __del__(self):
self.udp_socket.close()
class SaveChat(threading.Thread):
def __init__(self, queue):
super().__init__()
self.queue = queue
def run(self):
while True:
with open("./chat.txt", "a") as f:
chat_info = self.queue.get()
print("正在将(%s)写入到聊天记录文件中" % chat_info)
f.write(chat_info)
def main():
# 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定本地信息
udp_socket.bind(("", 7890))
# 创建一个FiFo的队列
q = queue.Queue()
# 创建线程对象
udp_r = RecvMsg(udp_socket, q)
udp_s = SendMsg(udp_socket, q)
chat_thread = SaveChat(q)
# 运行创建的子线程
udp_r.start()
udp_s.start()
chat_thread.start()
if __name__ == "__main__":
main()