(一) 线程概述
几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含多个顺序执流,每个顺序执行流就是一个线程。
进程和线程
所有运行中的任务通常对应一个进程(Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能。进程是系统进行资源分配调度的一个独立单位。
- 独立性: 进程是系统中独立存在的实体,他可以拥有自己的独立资源,每一个进程都拥有自己的私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:进程和程序的区别在于,程序是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入时间的概念。进程具有自己的什么周期和各种不同的状态,在程序中是没没有这个概念的。
- 并发性: 多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
注意:并发和并行是两个概念,并行指在同一时刻有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果。对于CPU在某个时间上只能执行一个程序,也就是运行一个进程,CPU不断地在这些进程之间轮换执行。但是相对于人的感觉CPU执行的速度太快了,所以在CPU虽然在不断的切换,但是用户感觉到好像有多个进程在同时执行。
现代的操作系统都支持多进程的并发执行,但在具体的实现细节上可能因为硬件和操作系统的不同而采取不同的策略,常用的有共享式的多任务操作策略,抢占式的多任务操作策略(效率较高)。
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程也被称为轻量级进程,线程是进程的执行单元。线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于大多数程序而言,通常仅需要一个主线程,但也可以在进程内创建多个顺序执行流,这些顺序执行流就是线程,每个线程都是独立的。 线程是进程的组成部分。
一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。
线程可以完成一定的任务,可以与其他线程共享父进程中的共享变量及部分环境,相互之间协同完成进程所要完成的任务。线程的运行是抢占式的,也就是说当前运行的线程在任何时候后可能被挂起,以便另外一个线程可以运行,一个线程可以创建和撤销另外一个线程,同一个进程中的多个线程可以并发运行。
操作系统可以同时执行多个任务,每一个任务就是一个进程;进程可以同时执行多个任务,每一个任务就是一个线程。
(二) 多线程的优势
线程在程序中是独立的,并发的执行流,而且线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高程序的运行效率。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享同一个进程的虚拟空间。线程共享的环境包括(进程代码段、进程的共有数据等)利用这些数据,线程之间可以很容易实现通信。操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此使用多线程来实现并发比使用多进程的性能要高的多。
- 进程之间不能共享内存,但线程之间共享内存非常容易
- 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
- python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统调度方式,从而简化了python的多线程编程。
(三)线程的调度和启动
python 中一个线程对应c语言中一个线程
gil使得同一个时刻只有一个线程运行在cpu上执行字节码,无法将多个线程映射到多个cpu上执行
gil会根据执行的字节码行数以及时间释放gil,在遇到io操作时会主动释放
python提供了_thread 和threading 两个模块来支持多线程,其中_thread提供低级别的,原始的线程支持,以及一个简单的锁,但是一般不建议使用_thread模块;而threading模块则提供了功能丰富的多线程支持。
python 主要提供两种方式来创建线程:
- 使用threading模块的Thread类的构造器创建线程
- 继承 threading模块的Thread类创建线程类
调用Thread类的构造器创建线程
调用Thread类的构造器创建线程,直接调用threading.Thread类的如下构造器创建线程。
__init__(self, group= None, target=None, name= None, args=(), kwargs= None, *, daemon= None)
target :指定该线程要调度的目标方法。只传函数名,不传函数,即不加()
args :指定一个元组,以位置参数的形式为target指定的函数传入参数。元组的第一个参数传给target的第一个,以此类推。
kwargs:指定一个字典,以关键字参数的形式为target指定的函数传入参数
daemon: 指定所构建的线程是否为后台线程。
调用Thread类的构造器创建并启动多线程的步骤:
-
调用Thread类的构造器创建线程对象。在创建线程对象时。target参数指定的函数将作为线程的执行实体
-
调用线程对象的start()方法启动线程。
import threading #定义一个普通action方法,该方法准备作为线程的执行实体 def action(max): for i in range(max): #调用threading模块的current_thread()函数获取当前线程 #调用线程对象的getName()方法获取单前线程的名字 print(threading.current_thread().getName() + " " +str(i)) #下面是主程序 for i in range(100): #调用threading模块的current_thread()函数获取当前线程 print(threading.current_thread().getName() + " " +str(i)) if i == 20: t1 = threading.Thread(target=action, args=(100,)) t1.start() t2 = threading.Thread(target =action, args=(100,)) t2.start() print("主线程执行完成!")
可以看到在循环变量达到20时,创建并且启动了两个新线程。所以此时一共有三个线程,但是三个线程之间的执行没有没有先后顺序,他们的执行方式为Thread-1执行一段时间Thread-2或MainThread获得CPU执行一段时间。
- threading.current_thread():它是threading模块的函数,该函数总是返回当前正在执行的线程对象
- getName() 它是Thread类的实例方法,该方法返回调用它的线程名字
-setName() 方法可以为线程设置名字,getName和setName 两个方法可通过name属性来代替。
继承Thread类创建线程类
-
定义Thread类的子类,并重写该类的run()方法。run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
-
创建Thread子类的实例,即创建线程对象
-
调用线程对象的start()方法来启动线程
class GetDetailHtml(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail html started") time.sleep(2) print("get detail html end") class GetDetailUrl(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail url started") time.sleep(2) print("get detail url end") if __name__ == "__main__": thread1 = GetDetailHtml("get_detail_html") thread2 = GetDetailUrl("get_detail_url") start_time = time.time() thread1.start() thread2.start() """join进行线程阻塞,只有两个线程都执行完成后才执行后边的""" thread1.join() thread2.join() print("last time :{}".format(time.time() - start_time))
class FKThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.i = 0
#重写run方法
def run(self):
while self.i < 100:
#调用threading模块的current_thread()函数获取当前线程
#调用线程对象的getName()方法获取当前线程的名字
print(threading.current_thread().getName() + " " + str(self.i))
self.i += 1
for i in range(100):
print(threading.current_thread().getName() + " " + str(i))
if i == 20:
#创建并且启动第一个线程
ft = FKThread()
ft.start()
ft2 = FKThread()
ft2.start()
print("主线程执行完成")
image.png
线程的生命周期
线程被创建以后不是直接就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,他要经历新建、就绪、运行、阻塞和死亡5种状态。
网友评论