06_多线程
1.线程概述
1.线程是实现多任务编程的一种方法,可以使用计算机多核资源,是计算机核心分配的最小单位,线程由代码段,数据段,和TCB(线程控制块)组成
2.线程又称为轻量级进程,在创建和删除时消耗的计算机资源小,理论上创建和销毁线程的消耗是创建和销毁进程消耗的二十分之一
3.一个进程中的所有线程共享进程的空间资源(空间,全局变量,分配的内存等),进程中每个线程有自己的特有属性,如指令集TID等
4.多线程程序的执行顺序是不确定的,主线程会等待所有的子线程结束后才结束
5.计算机开启的线程数量建议: CPU核数 * 5
2.threading模块语法概述
import threading t = thread.Thread() # 创建线程并返回线程对象 参数: target: 线程函数 args: 给线程函数的位置参数(类型为元组) kwargs: 给线程函数的字典传参(类型为字典) name: 给线程取名字(默认为Thread-1) t.start(): 启动线程 t.join(timeout): 回收线程 t.is_alive(): 查看线程状态 t.name: 查看线程名称 threading.currentThread(): 得到线程对象 t.setName(): 设置线程名称 t.daemon = True: 守护线程,默认为False主线程执行完毕不会影响分支线程的执行,True则表示主线程执行完毕其它线程也会终止 设置方法: t.daemon = True 或者 t.setDaemon(True) t.isDaemon(True): 判断daemon属性是 True or False
3.线程属性示例
from threading import Thread from threading import currentThread from time import sleep def func(sec): print("线程属性测试") sleep(sec) print("%s线程结束" % currentThread().getName()) def main(): thread = list() for i in range(3): t = Thread(name="t-" + str(i), target=func, args=(5,)) # 设置deamon属性为True,此时主线程结束,子线程也会结束 # t.setDaemon(True) # 默认为False,主线程等待子线程结束 print("isDaemon", t.isDaemon()) # 也可以在定义后设置线程名称 # t.setName = "t-" + str(i) t.start() thread.append(t) for i in thread: i.join(1) print("thread name:", i.name) print("alive:", i.is_alive()) print("主线程结束") if __name__ == '__main__': main() """执行结果 isDaemon False 线程属性测试 isDaemon False 线程属性测试 isDaemon False 线程属性测试 thread name: t-0 alive: True thread name: t-1 alive: True thread name: t-2 alive: True 主线程结束 t-0线程结束 t-2线程结束 t-1线程结束 """
4.线程定时器
from threading import Timer # 定时器 def func(): print('此生一入IT们,从此不爱任何人!') Timer(2.5, func).start() # Timer(time, func) # time: 睡眠的时间,以秒为单位 # func: 睡眠时间之后,需要执行的任务
5.验证守护线程
from threading import Thread import time def func(): time.sleep(2) print(123) def func1(): time.sleep(1) print('abc') # 守护线程不是根据主线程的代码执行结束而结束,而是根据主线程执行结束才结束 # 主线程会等待普通线程执行结束再结束,守护线程会等待主线程结束再结束,所以一般把不重要的事情设置为守护线程 # 守护进程是根据主进程的代码执行完毕,守护进程就结束 if __name__ == '__main__': t = Thread(target=func) t.daemon = True t.start() t1 = Thread(target=func1) t1.start() print(456)
6.自定义线程函数
# 将函数作为实参传递给线程类,通过线程类的实例化对象启动线程 import threading import time def sing(): """唱歌5秒钟""" for i in range(5): print("---正在唱歌---%s" % i) time.sleep(1) def dance(): """跳舞10秒钟""" for i in range(10): print("---正在跳舞---%s" % i) time.sleep(1) def main(): # 创建线程 t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) # 启动线程 t1.start() t2.start() for i in range(15): # 查看当前线程数量 print(threading.enumerate()) time.sleep(1) t1.join() # 等待回收线程 t2.join() # 等待回收线程 if __name__ == "__main__": main()
7.自定义线程类
1.继承Thread类重写 run 方法
import threading import time # threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法 # 而创建自己的线程实例后,通过Thread类的start方法启动该线程 class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字 print(msg) if __name__ == "__main__": t = MyThread() t.start()
2.继承Thread类重写 run 方法和 __init__ 方法
from threading import Thread from time import ctime from time import sleep class MyThread(Thread): def __init__(self, func, args, name="Tedu"): super().__init__() self.func = func self.args = args self.name = name def run(self): self.func(*self.args) @staticmethod # 声明此方法是静态方法 def plary(file, sec): for _ in range(sec): print("%s: %s" % (file, ctime())) sleep(sec) def main(): t = MyThread(MyThread.plary, ("echo", 3)) t.start() t.join() if __name__ == "__main__": main() """ echo: Sun Jul 19 23:13:55 2020 echo: Sun Jul 19 23:13:58 2020 echo: Sun Jul 19 23:14:01 2020 """
8.Python线程的全局解释器锁(GIL)
全局解释器锁-产生的原因
GIL是CPython解释器存在的问题
Python支持多线程 同步互斥 加锁 超级锁 但在同一时刻解释器只能解释一个线程
大量的Python库为了省事沿用了这种方法导致了Python多线程效率低下
全局解释器锁-用htop命令查看CPU占有率验证
1.单线程死循环
def main(): # 主线程死循环 while True: pass if __name__ == "__main__": main()
2.多线程死循环
import threading # 子线程死循环 def test(): pass def main(): t1 = threading.Thread(target=test) t1.start() # 主线程死循环 while True: pass if __name__ == "__main__": main()
3.多进程死循环
import multiprocessing # 子进程死循环 def test(): pass def main(): p1 = multiprocessing.Process(target=test) p1.start() # 主进程死循环 while True: pass if __name__ == "__main__": main()
全局解释器锁-通过计算密集耗时和IO操作耗时验证
import time import threading import multiprocessing # 计算密集 def calculation(num, x=1, y=1): for _ in range(num): x += 1 y += 1 # IO密集-读写 def io_write_read(num): # IO密集-写 f = open("./test.txt", "w") for _ in range(num): f.write("hello world!\n") f.close() # IO密集-读 f = open("./test.txt", "r") for _ in range(num): f.readline() f.close() # 单任务耗时 def single_task_time(num, func): t_start = time.time() for _ in range(10): func(num) t_end = time.time() - t_start print("单线程CPU执行1亿次%s耗时: %s" % (func.__name__, t_end)) # 多线程耗时 def threading_time(num, func): t_start = time.time() counts = list() for _ in range(10): # t = threading.Thread(target=func, args=(num,), kwargs={"x": 1, "y": 1}) t = threading.Thread(target=func, args=(num,)) t.start() counts.append(t) for i in counts: i.join() t_end = time.time() - t_start print("10个线程CPU执行1亿次%s耗时: %s" % (func.__name__, t_end)) # 多进程耗时 def multiprocessing_time(num, func): t_start = time.time() counts = list() for _ in range(10): p = multiprocessing.Process(target=func, args=(num,)) p.start() counts.append(p) for i in counts: i.join() t_end = time.time() - t_start print("10个进程CPU执行1亿次%s耗时: %s" % (func.__name__, t_end)) def main(): # 单线程耗时 single_task_time(5000000, calculation) # 单线程CPU执行1亿次calculation耗时: 5.152180910110474 single_task_time(5000000, io_write_read) # 单线程CPU执行1亿次io_write_read耗时: 22.120678186416626 # 多线程耗时 threading_time(5000000, calculation) # 10个线程CPU执行1亿次calculation耗时: 5.205517053604126 threading_time(5000000, io_write_read) # 10个线程CPU执行1亿次io_write_read耗时: 22.218310356140137 # 多进程耗时 multiprocessing_time(5000000, calculation) # 10个进程CPU执行1亿次calculation耗时: 1.7116732597351074 multiprocessing_time(5000000, io_write_read) # 10个进程CPU执行1亿次io_write_read耗时: 6.813052177429199 if __name__ == "__main__": main() """执行结果 单线程CPU执行1亿次calculation耗时: 5.335744142532349 单线程CPU执行1亿次io_write_read耗时: 21.98353886604309 10个线程CPU执行1亿次calculation耗时: 5.344666814804077 10个线程CPU执行1亿次io_write_read耗时: 22.28637194633484 10个进程CPU执行1亿次calculation耗时: 1.8069920539855957 10个进程CPU执行1亿次io_write_read耗时: 7.141486167907715 """
全局解释器锁-解决方法
1.不使用多线程,使用多进程
2.不使用C C++做的解释器,用 C# Java
3.Python多线程适合高用时的网络IO操作,不适用CPU密集型程序
4.使用其它语言实现多线程,Python去调用
GIL解决方案示例-C语言写子线程执行的函数由Python调用
loop.c文件代码
#include<stdio.h> void DeadLoop() { while(1) { ; } } int main(int argc, char **argv) { printf("start DeadLoop\n"); DeadLoop(); return 0; }
命令行下编译C语言写的代码
gcc loop.c # 编译成功后当前目录先会生成一个a.out的可执行文件
gcc loop.c -shared -o libdead_loop.so # 执行命令把c语言文件编译成一个动态库文件
./a.out # 执行文件
Python调用编译后的C语言的动态库文件
import ctypes import threading def main(): # 加载动态库 lib = ctypes.cdll.LoadLibrary("libdead_loop.so") # 创建一个子线程,让子线程执行C语言写的死循环函数 t = threading.Thread(target=lib.DeadLoop) t.start() if __name__ == "__main__": main()
9.多线程-udp聊天器
import socket import threading def send_msg(udp_socket): """获取键盘数据,并将其发送给对方""" while True: # 1. 从键盘输入数据 msg = input("\n请输入要发送的数据:") # 2. 输入对方的ip地址 dest_ip = input("\n请输入对方的ip地址:") # 3. 输入对方的port dest_port = int(input("\n请输入对方的port:")) # 4. 发送数据 udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) def recv_msg(udp_socket): """接收数据并显示""" while True: # 1. 接收数据 recv_msg = udp_socket.recvfrom(1024) # 2. 解码 recv_ip = recv_msg[1] recv_msg = recv_msg[0].decode("utf-8") # 3. 显示接收到的数据 print(">>>%s:%s" % (str(recv_ip), recv_msg)) def main(): # 1. 创建套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 绑定本地信息 udp_socket.bind(("", 7890)) # 3. 创建一个子线程用来接收数据 t = threading.Thread(target=recv_msg, args=(udp_socket,)) t.start() # 4. 让主线程用来检测键盘数据并且发送 send_msg(udp_socket) if __name__ == "__main__": main()
10.多线程-简单tcp服务器
from socket import * import os import sys from threading import Thread def client_handler(c): try: print("子线程接收%s客户端的请求" % str(c.getpeername())) while True: data = c.recv(1024) if not data: break print(data.decode("utf-8")) c.send(b"receive your message") except (KeyboardInterrupt, SystemError): raise except Exception as e: print(e) # 关闭客户端套接字 c.close() def main(): # 创建套接字 s = socket() # 端口重用 s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 绑定本地信息 s.bind(("", 7890)) # 监听 s.listen(128) print("主线程%d等待客户端的链接" % os.getpid()) while True: try: c, addr = s.accept() except KeyboardInterrupt: raise except Exception as e: print(e) continue t = Thread(target=client_handler, args=(c,)) t.setDaemon(True) # 主线程执行完毕其它线程也会终止 t.start() # 关闭监听套接字 s.close() if __name__ == "__main__": main()
11.多线程-简单tcp服务器的测试客户端
import socket def main(): # 创建数据流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务器 server_ip = input("请输入要连接的服务器的ip:") serve_port = int(input("请输入要连接的服务器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) # 发送数据 send_data = input("请输入要发生的数据:") tcp_client_socket.send(send_data.encode("utf-8")) # 接收服务器发送过来的数据 recv_data = tcp_client_socket.recv(1024) print("接收到的数据为:%s" % recv_data.decode("utf-8")) # 关闭套接字 tcp_client_socket.close() if __name__ == "__main__": main()
版权:本文版权归作者
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任