阅读内容
一、多任务
1.目的
多任务:在同一时间内执行多个任务,每个任务可以理解成现实生活中干的每个活。
2.形式
并发 :指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行
并行 :指的是任务数小于等于cpu核数,即任务真的是一起执行的
二、线程
1.线程概念
-
- 线程是运行的程序中一个执行流程 <分支/线索>
- 一个程序中默认存在一个线程 主线程mainthread, 新建的线程称为子线程
- 编写完程序后, 在这个程序中的所有代码都是主程序
2.创建线程
1.创建对象 : 对象 = threading.Thread(target=入口, args=(), kwargs={})
# args 传入的是位置参数 kwargs传入的关键字参数 args 后面 必须是元组形式的 字符
2.启动线程: 对象.start()
3.线程中的方法
-
- 查看存货的线程列表 :
- 阻塞等待子线程 : 对象.join()
- 查看当前线程 :
4.线程中的注意点
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading import time def sing(singer, address): """唱歌""" for i in range(2): print("%s正在%s唱 sing..." % (singer, address)) time.sleep(1) def dance(dancer, address): """跳舞""" for i in range(2): print("%s正在%s跳 dance..." % (dancer, address)) print(threading.enumerate()) time.sleep(1) def main(): """主线程""" # 创建子线程 先创建出来threading.Thread 类的对象 .start() # target 目标 指定子线程运行的入口函数 ; # args 用来指定子线程函数运行所需的 位置参数的元组 # kwargs 是指定子线程运行所需的 关键字参数的字典 sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'}) sing_thd.start() dance_thd = threading.Thread(target=dance, args=('mike',), kwargs={'address': '月亮之上'}) dance_thd.start() print("这是主线程最后的代码") # 结论 : 主线程创建出了子线程 主线程默认情况下不会直接退出 而是等待子线程结束后在一起退出 # 原因 : 主线程有义务去挥手子线程相应的资源 if __name__ == '__main__': main() #### 执行结果 #### 玲花正在鸟巢唱 sing... mike正在月亮之上跳 dance... [<_MainThread(MainThread, started 1916)>, <Thread(Thread-1, started 10688)>, <Thread(Thread-2, started 8036)>] 这是主线程最后的代码 mike正在月亮之上跳 dance... 玲花正在鸟巢唱 sing... [<_MainThread(MainThread, stopped 1916)>, <Thread(Thread-1, started 10688)>, <Thread(Thread-2, started 8036)>]
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading import time # threading.current_thread().name 打印当前线程名 def task(): for i in range(3): print("当前线程:", threading.current_thread()) time.sleep(1) if __name__ == '__main__': for _ in range(5): sub_thread = threading.Thread(target=task) sub_thread.start() print(threading.enumerate()) #### 运行结果 #### 当前线程: <Thread(Thread-1, started 2776)> 当前线程: <Thread(Thread-2, started 5684)> 当前线程: <Thread(Thread-3, started 8056)> 当前线程: <Thread(Thread-4, started 7700)> 当前线程: <Thread(Thread-5, started 9964)> 当前线程: <Thread(Thread-5, started 9964)> 当前线程: <Thread(Thread-4, started 7700)> 当前线程: <Thread(Thread-3, started 8056)> 当前线程: <Thread(Thread-2, started 5684)> 当前线程: <Thread(Thread-1, started 2776)> 当前线程: <Thread(Thread-1, started 2776)> 当前线程: <Thread(Thread-3, started 8056)> 当前线程: <Thread(Thread-5, started 9964)> 当前线程: <Thread(Thread-4, started 7700)> 当前线程: <Thread(Thread-2, started 5684)>
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading import time class MyThread(threading.Thread): """子类 将Thread类和线程函数封装在一起 提高代码的可维护性""" # 吐过需要在子类中运行某个代码 需要实现run方法 def run(self): """子线程需要运行的代码 一旦子线程启动运行 自动调用该方法""" print("这是子线程 %s" % (threading.enumerate())) self.sing() def sing(self): for i in range(5): print("这是子线程") time.sleep(1) class main(): """对象 = threading.Thread; 对象.start()""" mt = MyThread() mt.start() # 创建并启动子线程 调用run方法 # mt.run() # 这么编写代码 会执行run方法的代码 for i in range(5): print("这是主线程") time.sleep(1) if __name__ == '__main__': main()
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading import time def sing(singer, address): """唱歌""" for i in range(30): print("%s正在%s唱 sing..." % (singer, address)) time.sleep(1) def dance(dancer, address): """跳舞""" for i in range(50): print("%s正在%s跳 dance..." % (dancer, address)) time.sleep(1) def main(): """主线程""" # 创建子线程 先创建出来threading.Thread 类的对象 .start() # target 目标 指定子线程运行的入口函数 ; # args 用来指定子线程函数运行所需的 位置参数的元组 # kwargs 是指定子线程运行所需的 关键字参数的字典 sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'}) sing_thd.daemon = True sing_thd.start() dance_thd = threading.Thread(target=dance, args=('mike',), kwargs={'address': '月亮之上'}, daemon=True) # dance_thd.setDaemon(True) dance_thd.start() # 主线程结束后 子线程也跟着结束 print("main...") time.sleep(2) # 设置守护线程方法 # 1. 对象.setDaemon(True) # 在start之前调用 # 2. 对象.daemon = True # 在start之前调用 # 3. Thread(...,daemon = True) 这种方法只适用于python3 if __name__ == '__main__': main() #### 执行结果 #### 玲花正在鸟巢唱 sing... mike正在月亮之上跳 dance... main... 玲花正在鸟巢唱 sing... mike正在月亮之上跳 dance...
三、资源竞争
1.验证子线程是否可以修改全局变量
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading g_number = 100 def update_number(): """子线程 修改 全局变量""" global g_number g_number += 1 print("全局变量:%s" % g_number) def main(): # 创建一个子线程 用来修改全局变量的值 thd= threading.Thread(target=update_number) thd.start() # 阻塞等待子线程运行完成 thd.join() # 打印 print(g_number) # 结论: 同一个进程内部的多个线程 是共享全局变量的 if __name__ == '__main__': main() #### 执行结果 #### 全局变量:101 101
2.子线程修改全局变量的存在的问题
问题 : 同一个进程内部的多个线程 是共享全局变量的, 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading g_number = 0 def update_number1(): """子线程 修改 全局变量""" global g_number for i in range(1000000): g_number += 1 def update_number2(): """子线程 修改 全局变量""" global g_number for i in range(10000000): g_number += 1 def main(): # 创建一个子线程 用来修改全局变量的值 thd1= threading.Thread(target=update_number1) thd1.start() thd2= threading.Thread(target=update_number1) thd2.start() # 阻塞等待子线程运行完成 thd1.join() thd2.join() # 打印 print(g_number) # 结论: 同一个进程内部的多个线程 是共享全局变量的 # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争 if __name__ == '__main__': main() #### 执行结果 #### 1273332
3.解决问题的方法------互斥锁
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading g_number = 0 def update_number1(lock): """子线程 修改 全局变量""" global g_number for i in range(1000000): # 修改全局变量之前 应该先申请锁; 如果所已经被别人锁定了 那当前任务就会阻塞等待直到对方释放锁 lock.acquire() g_number += 1 # 修改完成之后 应该释放互斥锁 lock.release() def update_number2(lock): """子线程 修改 全局变量""" global g_number for i in range(1000000): lock.acquire() g_number += 1 lock.release() def main(): # 创建互斥锁对象 Lock好比一个类 所以后面要加括号 lock = threading.Lock() # 创建一个子线程 用来修改全局变量的值 thd1= threading.Thread(target=update_number1, args=(lock,)) thd1.start() thd2= threading.Thread(target=update_number1, args=(lock,)) thd2.start() # 阻塞等待子线程运行完成 thd1.join() thd2.join() # 打印 print(g_number) # 结论: 同一个进程内部的多个线程 是共享全局变量的 # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争 if __name__ == '__main__': main() #### 执行结果 #### 200000
4.互斥锁存在问题------死锁
# #!/usr/bin/env python # # _*_ coding:utf-8 _*_ # # Author:Mr.yang # 死锁 from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('%s 拿到了A锁' % self.name) mutexB.acquire() print('%s 拿到了B锁' % self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print('%s 拿到了B锁' % self.name) time.sleep(0.01) mutexA.acquire() print('%s 拿到了A锁' % self.name) mutexB.release() mutexA.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start() # # ''' # # 打印结果: # # Thread-1 拿到了A锁 # # Thread-1 拿到了B锁 # # Thread-1 拿到了B锁 # # Thread-2 拿到了A锁 # # '''
5.正确释放锁的方法
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import threading g_number = 0 def update_number1(lock): """子线程 修改 全局变量""" global g_number for i in range(100000): # 修改全局变量之前 应该先申请锁; 如果所已经被别人锁定了 那当前任务就会阻塞等待直到对方释放锁 with lock: g_number += 1 # 修改完成之后 应该释放互斥锁 def update_number2(lock): """子线程 修改 全局变量""" global g_number for i in range(100000): with lock: g_number += 1 def main(): # 创建互斥锁对象 Lock好比一个类 所以后面要加括号 lock = threading.Lock() # 创建一个子线程 用来修改全局变量的值 thd1= threading.Thread(target=update_number1, args=(lock,)) thd1.start() thd2= threading.Thread(target=update_number1, args=(lock,)) thd2.start() # 阻塞等待子线程运行完成 thd1.join() thd2.join() # 打印 print(g_number) # 结论: 同一个进程内部的多个线程 是共享全局变量的 # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争 if __name__ == '__main__': main() #### 执行结果 #### 200000
四、结论
1. 多线程顺序是无序的 随机执行
2. 主线程默认会等待子线程全部结束 才退出
3. 主线程会等待所有的子线程结束后才结束,如果需要可以设置守护主线程
4. 多个子线程执行的 过程中 会产生资源竞争问题 ---- 解决方法使用互斥锁
5. 互斥锁
优点 :
缺点 : 容易造成死锁问题 deadlock
五、案例
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang 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): """接受消息""" while True: res_data, addr = udp_socket.recvfrom(1024) print("用户%s传入的数据为:%s" % (str(addr), res_data.decode('gbk'))) def main(): # 1.创建UDP套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_socket.bind(('', 9999)) # 1.1 创建子线程 recv_thd = threading.Thread(target=recv_message, args=(udp_socket,), daemon=True) recv_thd.start() # 2.开始循环接受用户输入,1:发送 2:接受 3:退出; 其他重新输入 while True: op = input("1.发送数据 2.接受数据 3.退出 请选择:") if op == '1': send_message(udp_socket) elif op == '2': recv_message(udp_socket) elif op == '3': print('buy') break else: print("输出有误,重新输入") # 3.退出循环才关闭 udp_socket.close() if __name__ == '__main__': main()
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import socket import threading def clent_request(client_socket): """处理 用户客户端请求""" while True: # 5.使用分机进行深入的交流 - echo 回射服务器 recv_data = client_socket.recv(1024) print("接受数据:%s" % recv_data.decode('gbk')) 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) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 2.固定号码 - 固定端口 server_socket.bind(('', 8888)) # 3.安装客户服务系统 - 主动 -> 被动监听模式 server_socket.listen(128) while True: # 4.从等待服务区取出一个客户端用以服务 转接到分机 - 接受链接 # (<socket.socket 和客户端关联起来的套接字对象), raddr=('172.17.99.129', 53614)>, ('172.17.99.129', 53614)) client_socket, client_addr = server_socket.accept() print("接受来自%s的数据请求" % str(client_socket)) thd = threading.Thread(target=clent_request, args=(client_socket,)) thd.start() # 7.主机挂机 server_socket.close()
#!/usr/bin/env python # _*_ coding:utf-8 _*_ # Author:Mr.yang import socket # 1.买个电话 -- 创建TCP套接字 参数是 地址协议版本 套接字类型 tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.拨号 创建和服务器的链接 IP = input("IP地址:") port = int(input("Port:")) tcp_socket.connect((IP, port)) # 3.发送数据 while True: choose = input(">>>:") if choose == 'q': break tcp_socket.send(choose.encode()) # 4.接受数据 阻塞等待数据 recv返回值一般情况下 就是对方发送的数据; 如果对方断开了链接 返回值就是 '' recv_data = tcp_socket.recv(1024) if not recv_data: print("对方断开链接") else: print(recv_data.decode('gbk')) # 5.关闭套接字 tcp_socket.close()