2025年2月2日(多任务 线程)
dw 快速删除这个单词
并行,并发
主线程,子线程
这段代码展示了如何使用 Python 的 threading
模块来创建和启动一个新的线程。让我逐步解释一下这段代码:
1. 导入必要的模块
import threading
import time
threading
模块提供了用于创建和管理线程的功能。time
模块提供了时间相关的功能,这里我们使用了time.sleep()
来让线程暂停。
2. 定义 MyThread
类
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm " + self.name + ' @ ' + str(i)
print(msg)
print(self.test())
MyThread
继承自threading.Thread
类,这意味着MyThread
是一个线程类。run()
方法是Thread
类中一个重要的方法,当线程启动时,Python 会自动调用run()
方法。for i in range(3):
:线程会循环 3 次,每次都进行一次 1 秒的暂停(time.sleep(1)
)。self.name
:每个线程都有一个name
属性,表示线程的名字,默认会设置为类似Thread-1
,Thread-2
这样的名字。- 每次打印一个包含当前线程名字和循环索引的消息:
I'm Thread-1 @ 0
(具体的名字会根据线程编号变化)。 self.test()
会调用MyThread
类中的test()
方法,并打印其返回值。
3. test()
方法
def test(self):
print("nb")
test()
是一个简单的方法,它每次被调用时,会输出"nb"
。
4. 启动线程
if __name__ == '__main__':
t = MyThread()
t.start()
if __name__ == '__main__':
确保代码仅在作为主程序执行时才会运行,而在作为模块导入时不会执行。t = MyThread()
:创建MyThread
类的实例(即一个线程对象)。t.start()
:调用start()
方法启动线程,线程一旦启动,Python 会自动调用run()
方法。
代码执行过程:
- 代码执行时,会创建并启动一个新线程(
t.start()
),它会在run()
方法中执行。 - 线程在
run()
方法内循环 3 次,每次暂停 1 秒,打印类似以下的输出:I'm Thread-1 @ 0 nb I'm Thread-1 @ 1 nb I'm Thread-1 @ 2 nb
- 由于是线程,可能会有一些时间延迟或交替输出的现象,特别是当多个线程同时运行时。
总结
这段代码演示了如何通过继承 threading.Thread
创建自定义线程类,并重写 run()
方法来定义线程执行的任务。在每次循环中,线程会休眠 1 秒,打印当前线程的信息,并调用 test()
方法输出 "nb"
。
你看到输出 None
是因为 self.test()
调用了 test()
方法,但 test()
方法没有返回任何值,默认会返回 None
。在 Python 中,若一个函数没有显式的 return
语句,它会默认返回 None
。
在你的代码中,test()
方法的定义是这样的:
def test(self):
print("nb")
test()
方法只打印了 "nb"
,但并没有显式地返回任何东西。因此,当你在 run()
方法中调用 self.test()
时,print(self.test())
会输出 test()
的返回值,也就是 None
。
解决方法
如果你不想看到 None
,你有两种选择:
-
返回一个值而不是默认的
None
:
修改test()
方法,使其返回一个值(例如字符串),然后打印这个值:def test(self): print("nb") return "some value" # 返回你想要的值
这样修改后,
self.test()
会返回"some value"
,print(self.test())
就会打印"some value"
而不是None
。 -
只调用
test()
,不打印它的返回值:
如果你只是想调用test()
来执行它的副作用(打印"nb"
),但不关心它的返回值,你可以直接调用它,而不需要print(self.test())
:self.test() # 只调用,不打印返回值
这样就只会打印
"nb"
,而不会显示None
。
总结
- 你看到
None
是因为test()
方法没有返回任何值,默认返回None
,然后被print()
打印出来。 - 你可以选择返回一个值,或者直接调用
test()
,以避免输出None
。
多线程共享全局变量
资源竞争
从错误信息来看,问题出在你的UDP套接字在一个线程中被关闭后,另一个线程仍然尝试使用它。这导致了 OSError: [WinError 10038] 在一个非套接字上尝试了一个操作
的错误。
以下是一些可能的解决方案和改进建议:
- 线程同步:在关闭套接字之前,确保所有线程都已经完成了它们的任务。你可以使用线程同步机制,比如
threading.Event
来协调线程的关闭。 - 修改退出条件:在
send_msg
和recv_msg
函数中,使用一个共享的退出标志来控制何时退出循环,而不是直接关闭套接字。 - 关闭套接字的时机:在主线程中等待所有子线程完成后再关闭套接字。
以下是一个改进后的代码示例:
import socket
import threading
def send_msg(udp_socket, stop_event):
while not stop_event.is_set():
send_data = input("输入:")
if send_data == "1":
stop_event.set()
break
udp_socket.sendto(send_data.encode("utf-8"), ("192.168.0.178", 8080))
def recv_msg(udp_socket, stop_event):
while not stop_event.is_set():
try:
recv_data, _ = udp_socket.recvfrom(1024)
message = recv_data.decode("utf-8")
print(message)
if message == "1":
stop_event.set()
break
except OSError:
break
def main():
# 1. 创建udp
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定端口
addr = ("", 7890)
udp_socket.bind(addr)
# 创建一个事件对象用于线程间通信
stop_event = threading.Event()
# 多线程
t_send = threading.Thread(target=send_msg, args=(udp_socket, stop_event))
t_recv = threading.Thread(target=recv_msg, args=(udp_socket, stop_event))
t_send.start()
t_recv.start()
# 等待线程结束
t_send.join()
t_recv.join()
# 5. 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
关键改进点:
- 使用
threading.Event
对象stop_event
来在两个线程之间共享退出信号。 - 在
recv_msg
中添加了异常处理,以防止在套接字关闭后继续尝试接收数据。 - 在主线程中使用
join()
方法等待两个子线程结束后再关闭套接字。
多线程udp聊天
import socket
import threading
import time
def send_msg(udp_socket):
while True:
send_data = input("输入:")
if send_data == "1":
print("发送结束")
break
udp_socket.sendto(send_data.encode("utf-8"), ("192.168.0.178", 8080))
def recv_msg(udp_socket):
while True:
recv_data = udp_socket.recvfrom(1024)
print(recv_data[0].decode("utf-8"))
if recv_data[0].decode("utf-8") == "1":
print("接收结束")
break
def main():
# 1. 创建udp
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定端口
addr = ("", 7890)
udp_socket.bind(addr)
# 多线程
t_send = threading.Thread(target=send_msg, args=(udp_socket,)) # 3. 发送数据
t_recv = threading.Thread(target=recv_msg, args=(udp_socket,)) # 4 .接收数据
t_send.start()
t_recv.start()
# 5. 关闭套接字
while True:
if len(threading.enumerate()) == 1:
print(threading.enumerate())
break
time.sleep(2)
udp_socket.close()
if __name__ == "__main__":
main()