优点: 同时执行多个任务 提高程序的执行效率 用户的体验
在同一cpu上同一时间段内执行的多任务方式
线程thread是一种实现多任务的手段
线程是运行的程序中一个执行流程 <分支/线索>
一个程序中默认存在一个线程 主线程mainthread, 新建的线程称为子线程
之前我们编写的所有的代码都是在主线程中运行的
创建对象 对象 = threading.Thread(target=入口, args=(), kwargs={})
启动线程 对象.start()
存活线程列表 threading.enumerate()
阻塞等待子线程 对象.join()
import threading
import time
def sing():
"""唱歌"""
for i in range(3):
print("正在唱月亮之上....")
time.sleep(1)
def dance():
"""跳舞"""
for i in range(3):
print("正在跳太空漫步....")
time.sleep(1)
def main():
"""主线程"""
# 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
# target目标 执行子线程运行的入口函数
# sing_thd = threading.Thread(target=dance()) # 错误的
sing_thd = threading.Thread(target=dance)
sing_thd.start()
sing()
if __name__ == '__main__':
main()
thread类参数:
import threading
import time
def sing():
"""唱歌"""
for i in range(3):
print("正在唱月亮之上....")
time.sleep(1)
def dance(dancer, address):
"""跳舞"""
for i in range(3):
print("%s正在%s跳太空漫步...." % (dancer, address))
time.sleep(1)
def main():
"""主线程"""
# 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
# target目标 执行子线程运行的入口函数 ;
# args是指定子线程函数运行所需的位置参数的元组
# kwargs是指定子线程运行所需的 关键字参数的字典
# sing_thd = threading.Thread(target=dance()) # 错误的
dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
dance_thd.start()
sing()
if __name__ == '__main__':
main()
创建两个子线程:
import threading
import time
def sing(singer, address):
"""唱歌"""
for i in range(3):
print("%s正在%s唱月亮之上...." % (singer, address))
time.sleep(1)
def dance(dancer, address):
"""跳舞"""
for i in range(3):
print("%s正在%s跳太空漫步...." % (dancer, address))
time.sleep(1)
def main():
"""主线程"""
# 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
# target目标 执行子线程运行的入口函数 ;
# args是指定子线程函数运行所需的位置参数的元组
# kwargs是指定子线程运行所需的 关键字参数的字典
# sing_thd = threading.Thread(target=dance()) # 错误的
dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
dance_thd.start()
# sing()
sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
sing_thd.start()
# 了解 - 查看当前程序所有存活的线程列表
print(threading.enumerate())
time.sleep(5)
print(threading.enumerate())
if __name__ == '__main__':
main()
def task():
for i in range(3):
print("当前线程名称是:", threading.current_thread().name)
time.sleep(1)
if __name__ == '__main__':
for _ in range(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()
#当前线程名称是: Thread-1
#当前线程名称是: Thread-2
#当前线程名称是: Thread-4
#当前线程名称是: Thread-3
#当前线程名称是: Thread-5
#当前线程名称是: Thread-1
#当前线程名称是: Thread-3
#当前线程名称是: Thread-5
#当前线程名称是: Thread-4
#当前线程名称是: Thread-2
# 结论:主线程创建出了子线程 主线程运行完代码后默认情况下不会直接退出 而是等待子线程运行完成后再一起退出
# 原因:主线程有义务回收子线程相应的资源
阻塞等待:
def main():
"""主线程"""
# 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
# target目标 执行子线程运行的入口函数 ;
# args是指定子线程函数运行所需的位置参数的元组
# kwargs是指定子线程运行所需的 关键字参数的字典
# sing_thd = threading.Thread(target=dance()) # 错误的
dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
dance_thd.start()
# 阻塞等待 跳舞子线程运行完成 才会继续往下执行
dance_thd.join()
# sing()
sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
sing_thd.start()
# 阻塞等待 唱歌子线程运行完成 才会继续往下执行
sing_thd.join()
print("感谢诛仙天雷滚滚")
上面的代码会先执行跳舞的操作,然后执行唱歌的操作,最后主线程等待子线程全部完成后,进行输出打印结果。
上面的线程类thread类,不好维护
将线程类和函数功能封装在一起
面向对象特点:封装、多态、继承
优点: 将线程类 和线程的功能 封装类中
提高代码的可维护性
实现:
子类继承Thread类
实现子类中run方法-子线程入口
创建子类的对象
调用子类对象.start() 启动线程的创建和执行
class MyThread(threading.Thread):
"""子类 将Thread类和线程函数封装在一起 提高代码的可维护性"""
# 如果需要在子线程 中运行某个代码 需要实现run方法
def run(self):
"""run方法中存放就是 子线程需要运行的代码 一旦子线程启动运行 自动调用该方法"""
print("这是子线程 %s" % (threading.enumerate()))
self.sing()
def sing(self):
for i in range(5):
print("这是子线程")
time.sleep(1)
def main():
"""主线程 对象 = threading.Thread(); 对象.start() """
mt = MyThread()
mt.start() # 创建并且启动子线程 调用run方法
# mt.run() # 这么编写代码 会执行run方法的代码 是在主线程中执行
需要需要子线程跟随主线程一起退出
把所有的子线程设置为daemon线程 然后一旦主线程执行完成 所有子线程全部立即都退出
设置
1.对象.setDaemon(True) # 在start之前调用
sing_the.setDaemon(True)
sing_the.start()
2. 对象.daemon = True # 在start之前调用
kongfu_the.daemon = True
kongfu_the.start()
3. Thread(,,,daemon=True) #只能用以Py3
kongfu_the = threading.Thread(target=kongfu,args=('小闫',),kwargs={'kongfu_name':'葵花宝典'},daemon = True)
多线程共享全局变量 无序
导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争
验证多线程是否共享全局变量;
import threading
g_number = 1000
def update_number():
"""子线程函数 修改 全局变量g_number值"""
global g_number
g_number += 1
print("获取到全局变量的值是%d" % g_number)
def main():
# 创建一个子线程 修改全局变量的值
thd = threading.Thread(target=update_number)
thd.start()
# 阻塞等待子线程运行完成
thd.join()
# 打印
print("获取到全局变量的值是%d" % g_number)
# 结论: 同一个进程内部的多个线程 是共享全局变量的
if __name__ == '__main__':
main()
多线程同步 - 保持秩序
目的: 解决资源竞争问题
import threading
g_number = 0
def update_number1(lock):
"""子线程函数 修改 全局变量g_number值"""
global g_number
for i in range(1000000):
# 2 修改全局变量之前 应该先申请锁 ; 如果锁已经被别人锁定了 那当前任务就会阻塞等待直接对方释放锁
lock.acquire()
g_number += 1
# 3 修改完成之后 应该释放互斥锁
lock.release()
def update_number2(lock):
"""子线程函数 修改 全局变量g_number值"""
global g_number
for i in range(1000000):
# 2 修改全局变量之前 应该先申请锁 ; 如果锁已经被别人锁定了 那当前任务就会阻塞等待直接对方释放锁
lock.acquire()
g_number += 1
# 3 修改完成之后 应该释放互斥锁
lock.release()
def main():
# 1 创建互斥锁对象mutex Lock类
lock = threading.Lock()
# 创建一个子线程 修改全局变量的值
thd1 = threading.Thread(target=update_number1, args=(lock,))
thd1.start()
thd2 = threading.Thread(target=update_number2, args=(lock,))
thd2.start()
# 阻塞等待子线程运行完成
thd1.join()
thd2.join()
# 打印
print("获取到全局变量的值是%d" % g_number)
# 结论: 同一个进程内部的多个线程 是共享全局变量的
if __name__ == '__main__':
main()
互斥锁 是一种实现多线程同步的手段 最终目的为解决资源竞争
特点: 保证同一时刻只有一个任务能够成功占有锁
两种状态:锁定locked 和 未锁定
创建互斥锁对象
lock = threading.Lock()
申请互斥锁 - 阻塞等待
lock.acquire()
释放互斥锁
lock.release()
优点:
能够保证代码的正确执行 不会产生资源竞争问题
缺点:
降低了多任务执行的效率
容易造成死锁问题 deadlock
互斥锁没有被正确的释放 容易造成 死锁问题
可以考虑使用with自动的 锁的申请和释放
def get_value(index):
# 上锁
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
# 判断下标释放越界
if index >= len(my_list):
print("下标越界:", index)
lock.release()
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 释放锁
lock.release()
# 使用with能够 对互斥锁自动进行申请和释放 防止出错出现死锁 提高代码可读性
def get_value(index):
with lock:
print(threading.current_thread())
my_list = [3,6,8,1]
# 判断下标释放越界
if index >= len(my_list):
print("下标越界:", index)
return
value = my_list[index]
print(value)
time.sleep(0.2)
import socket
import threading
def send_message(udp_socket):
"""发送消息"""
# 需要用户输入数据 收件人IP 端口
data = input("数据:")
IP = input("IP:")
port = int(input("端口:"))
udp_socket.sendto(data.encode(), (IP, port))
def recv_message(udp_socket):
"""接收消息"""
# remote远程
while True:
recv_data, remote_address = udp_socket.recvfrom(1024)
print("接收到来自%s的数据%s" % (str(remote_address), recv_data.decode()))
def main():
# 1 创建UDP套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(('', 8888))
# 1.5 创建子线程 专门接收UDP数据
recv_thd = threading.Thread(target=recv_message, args=(udp_socket,))
recv_thd.daemon = True # 让子线程跟随主线程一起退出
recv_thd.start()
# 2 开始循环接收用户的输入 1 发送数据 ; 2接收数据 3退出程序; 其他报错重新输入
while True:
op = input("1 发送数据\n2 接收数据\n3 退出 请选择:")
if op == '1':
send_message(udp_socket)
# elif op == '2':
# recv_message(udp_socket)
elif op == '3':
print("Bye......")
break
else:
print("输入错误 重新输入")
# 3 退出循环后关闭套接字对象
udp_socket.close()
if __name__ == '__main__':
main()
import socket
import threading
def client_request(client_socket):
"""处理 客户端的请求"""
while True:
# 5 使用分机进行深入交流 echo回射
recv_data = client_socket.recv(1024)
print("接收到了数据:%s" % recv_data.decode())
client_socket.send(recv_data)
# client_socket.send(recv_data.decode('gbk').encode())
if not recv_data:
print("客户端下线了")
break
# 6 分机挂机
client_socket.close()
if __name__ == '__main__':
# 1 总机 - 创建TCP套接字<服务器套接字 监听套接字>
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 可以立即重用 套接字资源(IP和端口) 而不需要等待2MSL时间
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2 固定号码 - 固定端口
server_socket.bind(('', 10088))
# 3 安装客户服务系统 - 主动-> 被动监听模式
server_socket.listen(128)
while True:
# 4 从等待服务区取出一个客户端用以服务 转接到分机 - 接受连接
# (和客户端关联起来的套接字对象<socket.socket>, 客户端套接字地址('192.168.33.110', 46080))
client_socket, client_address = server_socket.accept()
print("接受到了来自%s的连接请求" % str(client_address))
# client_request(client_socket)
thd = threading.Thread(target=client_request, args=(client_socket,))
thd.start()
# 7 总机挂机
server_socket.close()