python学习笔记 day38 线程
1. 线程
线程是CPU调度的最小单位,进程是CPU分配资源的最小单位;
每个进程中至少有一个线程;(所以进程是包含线程的,同一进程的不同线程之间数据是共享的);
开启进程的时间要比开启线程的时间长,CPU在进程之间的切换比在线程之间的切换要慢很多;
如果有两个任务需要共享内存,有需要实现异步,就需要开启多线程;
如果两个任务需要数据隔离--就需要开启多进程;
多线程的优点:
1. 轻量级;
2. 同一个进程的多个线程之间的数据是共享的;
进程与线程之间的区别:
1. 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的多线程之间共享数据资源,某进程内的线程在其他进程不可见;
2. 通信:进程间通信IPC(比如队列,管道),线程之间可以直接读写线程数据段(如全局变量)来进行通信;
3. 调度和切换:线程上下文切换,比进程上下文切换要快得多;
4. 在多线程操作系统中,进程不是一个可执行的实体;(因为线程是CPU调度任务的最小单位,进程只是CPU资源分配的最小单位)
2. GIL--全局解释器锁
其实python在多线程上并没有实现真正的多并发,python的解释器加了一把锁,使得同一时间只有一个线程被CPU执行(实际上是为了保证数据的安全);
如果是在高计算(不涉及IO操作,阻塞的)可以开多进程来实现;
如果是在高IO(input print 文件的读写,网络通信accept recv等)可以开多线程(比如在网络爬虫,其实IO操作等待的时间是很长的,即使真的有多线程并发,也是需要等的,所以这种情况下多线程并发执行并不会带来效率的提升)
3. 简单的开启多线程
# 效果就是先打印主线程的进程id然后睡一秒之后同时打印10个线程的执行结果,可以看出10个线程之间是异步的,但是其实并不是真正的高并发; # 而且如果子线程中执行代码不睡一秒,那么打印顺序是先打印10个线程的执行结果,然后才打印主线程,就是因为开启线程的时间太短了 from threading import Thread import time import os def func(i): time.sleep(1) # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了 print("%s-hello,xuanxuan-%s"%(i,os.getpid())) # 开多线程时不用写在if __name__=="__main__":中 for i in range(10): t=Thread(target=func,args=(i,)) # 创建10个线程对象 t.start() # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁) print(os.getpid()) # 其实主线程跟开的10个子线程之间也是异步的
运行结果:
如果是想让主线程和多个子线程之间同步(也就是想先让多个子线程执行完毕,然后在继续执行主线程的代码):可以使用join()方法,但是注意要把开的多个线程放在一个列表中,最后统一使用join()方法关闭(这样才能实现多个线程之间的异步并发效果)否则开启一个线程就join()一下 这样多个线程之间就变为同步执行;
from threading import Thread import time import os def func(i): time.sleep(1) # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了 print("%s-hello,xuanxuan-%s"%(i,os.getpid())) # 开多线程时不用写在if __name__=="__main__":中 t_lst=[] # 存放开启的多个线程 for i in range(10): t=Thread(target=func,args=(i,)) # 创建10个线程对象 t.start() # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁) t_lst.append(t) [t.join() for t in t_lst] # 最后统一关闭多个线程,仍然保证多个线程之间异步并发 print(os.getpid()) # 使用join()方法之后,主线程和多个子线程之间就变为同步,就是主线程得先等待子线程执行完毕,然后才执行主线程中的代码
运行结果:
4. 开启线程的另一种方式----使用类
from threading import Thread import os class MyThread(Thread): # 自定义的类必须继承自Thread类 def run(self): # 必须在类内实现run()方法,线程创建之后 一旦执行t.start() 实质上就是执行类中的run()方法 print("hello,xuanxuan-%s"%os.getpid()) for i in range(10): t=MyThread() # 创建10个线程 t.start() print("主线程:%s"%os.getpid())
运行结果:
1. 如果在主线程中开了多个子线程,想统计开了多少个子线程:
from threading import Thread import os class MyThread(Thread): _count=0 # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的 def run(self): # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法 MyThread._count+=1 # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1 print("hello,xuanxuan-%s"%os.getpid()) for i in range(10): t=MyThread() # 创建10个线程(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数) t.start() # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1 print("主线程:%s"%os.getpid()) print("主线程中开启的子线程数目:",t._count) # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)
运行结果:
2. 如果想子线程执行的函数,需要参数,应该在类中怎么实现:(在类中继承父类的__init__()方法:super().__init__(),然后再写派生属性)
from threading import Thread import os import time import random class MyThread(Thread): _count=0 # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的 def __init__(self,arg1,arg2): # 子线程执行函数run()时,如果想传一个参数: super().__init__() # 不能重写__init__()方法,必须继承自父类Thread的__init__() 然后再写派生方法 self.arg1=arg1 self.arg2=arg2 def run(self): # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法 MyThread._count+=1 # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1 time.sleep(random.random()) print("%s-%s-%s"%(os.getpid(),self.arg1,self.arg2)) t_lst=[] # 想把开启的子线程都放在列表中,然后统一t.join()是为了保证开启的多个子线程仍然是异步的 # 只不过是想在子线程执行完毕之后,执行主线程的代码(只是主线程和多个子线程之间同步,多个子线程之间仍然是异步并发的) for i in range(10): t=MyThread(i,i*"*") # 创建10个线程(可以传参的)(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数) t.start() # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1 t_lst.append(t) [t.join() for t in t_lst] # 主线程会等待子线程执行完毕,才会继续执行下面的代码,但是子线程之间仍然是异步并发的 print("主线程:%s"%os.getpid()) print("主线程中开启的子线程数目:",t._count) # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)
运行结果:
3. 最后介绍在第一种开启线程的方法的一些其他的方法:
import threading import time def func(): time.sleep(1) print("hello,xuanxuan-线程名:%s-线程id: %s"%(threading.current_thread().name,threading.current_thread().ident)) # threading.current_thread().name----返回当前线程名 threading.current_thread().ident----返回当前线程id for i in range(10): t=threading.Thread(target=func) # 创建10个线程 t.start() # 开启线程 print(threading.enumerate()) # 打印当前正在执行的线程列表,包括主线程和开启的所有子线程 print("正在执行的线程数:",len(threading.enumerate())) # 打印正在执行的线程数 print("正在执行的线程数:",threading.active_count()) # 也是打印正在执行的线程数
运行结果:
4. 作业----使用多线程实现server端和多个客户端的通信
# server.py from threading import Thread import socket sk=socket.socket() # sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(("127.0.0.1",8080)) sk.listen() def func(conn): conn.send(bytes("hello,xuanxuan".encode("utf-8"))) ret=conn.recv(1024).decode("utf-8") print(ret) conn.close() while True: conn,addr=sk.accept() t=Thread(target=func,args=(conn,)) t.start() sk.close()
# client.py import socket sk=socket.socket() sk.connect(("127.0.0.1",8080)) ret=sk.recv(1024).decode("utf-8") print(ret) info=input(">>>") sk.send(bytes(info.encode("utf-8"))) sk.close()
运行结果: