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