3.1多任务
并行: 任务数小于或者等于cpu的核数就是并行,多个任务真正意义一起执行,提示:只有多核cpu才有并行的操作
并发: 任务数大于cpu的核数,多个任务看起来是一起执行,其实是假象,真正意义上多个任务交替轮流执行
3.2线程
3.2.1创建线程
import threading
sub_thread = threading.Thread(group=None, target=show_info, name="mythread", args=("杨钰莹", 18))
sub_thread.start()
参数介绍:
(1)group:线程组,注意点:目前group只能使用None,不能修改成其它值。默认group=None
(2)target:执行的目标函数
(3)name: 线程的名字,默认格式Thread-1
(4)args: 以元组方式给目标函数传参
(5)kwargs: 以字典的方式传参
例子:
sub_thread = threading.Thread(target=show_info, kwargs={"name": "陈晓", "age": 28})
sub_thread.start()
sub_thread = threading.Thread(target=show_info, args=("陈晓",), kwargs={"age": 28})
sub_thread.start()
3.2.2查看线程
#获取程序中活动线程的列表
# 提示:只有线程启动,才会放到活动线程的列表里面
thread_list = threading.enumerate()
print("11--", thread_list)
#扩展--获取当前活动线程的个数
active_count = threading.active_count()
print("333--", thread_list, len(thread_list), active_count)
# 扩展-获取当前代码执行的线程
current_thread = threading.current_thread()
print("sing:", current_thread)
3.2.3线程注意点
(1)线程之间的执行顺序是无序的,是由cpu调度决定的,线程是执行代码的
(2)主线程会等待所有的子线程执行完成以后程序再退出
#不让主线程等待方法实现:
#daemon:表示创建的子线程守护主线程,主线程退出后子线程直接销毁
work_thread = threading.Thread(target=work, daemon=True)
work_thread.start()
work_thread = threading.Thread(target=work)
# 设置成为守护主线程,主线程退出后子线程直接销毁
work_thread.setDaemon(True)
work_thread.start()
3.2.4自定义线程
import threading
# 自定义线程类
class MyThread(threading.Thread):
# 提供构造方法
def __init__(self, info1, info2):
# 注意点:提供构造方法不会调用父类的构造方法,需要手动自己调用
# 建议:以后如果自己提供了构造方法,自己需要手动调用父类的构造方法
super(MyThread, self).__init__()
self.info1 = info1
self.info2 = info2
# 封装一系列线程相关的任务
def show_info1(self):
print(self.info1)
def show_info2(self):
print(self.info2)
# 提示:自定义线程执行任务统一在run方法里面执行
# 注意点:重写父类的run方法
def run(self):
print("start")
self.show_info1()
self.show_info2()
# 创建自定义线程对象
# 提示: 自定义线程创建对象的时候不要指定target,因为执行任务都是在run方法里面执行的
my_thread = MyThread("show_info1", "show_info2")
# 注意点:线程启动统一使用start方法,因为start内部调用了run方法
my_thread.start()
3.2.5多线程共享全局变量
import threading
import time
# 全局变量
my_list = list() #=> [] 元组, 列表, 字典, 集合,range,字符串都是类
# 向全局变量里面添加数据的任务
def add_data():
# 在此不需要加上global是因为全局变量的内存地址没有发生改变,所以不需要加上global
for i in range(5):
my_list.append(i)
print(id(my_list), i)
time.sleep(0.1)
print("add:", my_list)
# 向全局变量里面读取数据
def read_data():
print("read:", my_list)
if __name__ == '__main__':
# 创建添加数据的线程
add_thread = threading.Thread(target=add_data)
# 创建读取数据的线程
read_thread = threading.Thread(target=read_data)
# 启动线程
add_thread.start()
# 主线程延时1秒后再继续执行下面的代码
time.sleep(1)
read_thread.start()
3.2.6多线程共享全局变量-问题
(1)如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
(2)解决方法(两种):
# ①设置延时
first_thread.start()
time.sleep(1)
second_thread.start()
# Or:
# ②线程等待
# 主线程等待第一个线程执行完成以后那么代码再继续往下执行
first_thread.start()
first_thread.join() # 线程同步:按照预先定义好的顺序一个任务执行完成另外一个任务再去执行
print("主线程等待第一个线程执行完成了")
second_thread.start()
3.2.7互斥锁
互斥锁能保证同一时刻只有一个线程去执行代码,具体哪个线程抢到这个锁我们决定不了,但是能够保证数据的准确性
注意点: 加上互斥锁多任务瞬间变成单任务,执行效率会降低
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁
lock = threading.Lock()
# 锁定
lock.acquire()
# 释放
lock.release()
3.2.8死锁
一直等待对方释放锁的情景叫做死锁。要避免死锁的出现,及时释放锁。
3.2.9案例:多任务版udp聊天器
import socket
import threading
# 发送数据的功能函数
def send_msg(udp_socket):
# 接收用户发送的数据
send_content = input("请输入您要发送的数据:")
# 对字符串进行gbk进行编码
send_data = send_content.encode("gbk")
# 接收用户输入的对方ip
dest_ip = input("请输入对方的ip地址:")
# 接收用户输入的对方端口号
dest_port = int(input("请输入对方的端口号:"))
# 发送数据
udp_socket.sendto(send_data, (dest_ip, dest_port))
# 接收数据的功能函数
def recv_msg(udp_socket):
while True:
# 接收数据
recv_data, ip_port = udp_socket.recvfrom(1024)
# 对二进制数据进行解码
recv_content = recv_data.decode("gbk")
print(recv_content, ip_port)
if __name__ == '__main__':
# 创建udpsocket
# 全局变量
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(("", 9090))
# 创建子线程接收数据
recv_thread = threading.Thread(target=recv_msg, args=(udp_socket,))
# 设置守护主线程,主线程退出子线程直接销毁
recv_thread.setDaemon(True)
# 启动线程
recv_thread.start()
while True:
# 接收用户的指令
menu_option = input("请输入功能选项 1:发送 3:退出 >>")
if menu_option == "1":
# 发送数据
send_msg(udp_socket)
elif menu_option == "3":
break
# 关闭socket
udp_socket.close()
3.2.10udp发送广播消息
import socket
if __name__ == '__main__':
# 创建udpsocket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置socket选项, 开启发送广播消息的功能
# 1. SOL_SOCKET:当前socket
# 2. SO_BROADCAST: 广播选项
# 3. True:开启发送广播消息功能
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
# 192.168.131.255: 只给131网段发送广播消息
# 255.255.255.255: 发送广播消息不区分网段
# 发送广播消息
udp_socket.sendto("大家好".encode("gbk"), ("255.255.255.255", 9090))
# 关闭socket
udp_socket.close()
3.2.11tcp服务端框架
import socket
import threading
# 接收客户端的数据
def recv_msg(ip_port, service_client_socket):
print(ip_port)
while True:
# 接收客户端的消息
recv_data = service_client_socket.recv(1024)
# 判断数据是否为空
if recv_data:
# 对数据进行解码
recv_content = recv_data.decode("gbk")
print(recv_content)
service_client_socket.send("ok,问题正在处理中...".encode("gbk"))
else:
print("客户端发送数据完成,客户端下线了:", ip_port)
break
# 关闭服务于客户端的套接字
service_client_socket.close()
if __name__ == '__main__':
# 创建tcp服务端socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 立即释放端口或者重用端口号
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口号
tcp_server_socket.bind(("", 8989))
# 设置监听,把服务端套接字由主动套接字改成被动套接字,被动套接字只能接收客户端的连接请求,不能收发消息
tcp_server_socket.listen(128)
while True:
# 等待接收客户端连接请求
service_client_socket, ip_port = tcp_server_socket.accept()
# 创建子线程
recv_thread = threading.Thread(target=recv_msg, args=(ip_port, service_client_socket))
# 守护主线程,主线程退出子线程直接销毁
recv_thread.setDaemon(True)
# 启动线程执行接收数据的任务
recv_thread.start()
# 关闭服务端套接字
tcp_server_socket.close()