前言
国庆假期回来上班第一天,日常犯困。
一、进程和线程的区别
1-1、进程
- 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。是操作系统资源分配的基本单元。
1-2、线程
- 线程:进程中的一个执行单元,一个进程至少有一个线程,一个进程可以运行多个线程。是比进程更小的独立运行的基本单元,故也被成为轻量级进程。(协程是一种比线程更轻量级的存在,一个线程可以拥有多个协程。)
1-3、区别
- 区别:
1、根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
2、资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
3、包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
4、内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
5、影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
6、执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。
综上所述,进程和线程都有各自的优缺点,操作系统会根据应用程序的需求和系统资源的限制来选择合适的方式来实现多任务。
二、使用threading 模块来创建多线程
2-0、threading模块介绍
Python的 threading 模块提供了线程相关的类和方法,可以在 Python 程序中创建和控制多个线程,实现多任务并发执行。
该模块主要包含以下类和方法:
- Thread 类:表示一个线程,可以通过继承该类来创建自定义的线程类,并实现自己的 run 方法来定义线程的具体行为。
- Lock 类:表示一个锁对象,用于控制多个线程对共享资源的访问。可以使用 acquire 和 release 方法来加锁和释放锁。
- RLock 类:表示可重入锁对象,与 Lock 类类似,但可以在同一个线程中多次获取锁而不会导致死锁。
- Condition 类:表示条件变量对象,用于在线程之间进行协调和同步,可以使用 wait、notify 和 notify_all 方法来等待和通知其他线程。
- Event 类:表示事件对象,用于线程之间的通信和同步,可以使用 set 和 clear 方法来设置和清除事件状态,使用 wait 方法来等待事件触发。
- Timer 类:表示定时器对象,用于在指定时间后触发一个函数,可以使用 start 和 cancel 方法来启动和取消定时器。
使用 threading 模块可以方便地创建和控制多个线程,实现并发执行的程序。但需要注意多线程编程可能存在的竞态条件、死锁等问题,需要合理使用锁、条件变量等同步机制来保证程序的正确性和稳定性。
2-1、使用threading.Thread()方法开启线程
使用 threading.Thread() 方法可以创建线程对象并启动线程。以下是详细的使用方法:
- 创建 Thread 对象
创建 Thread 对象时,需要提供一个可调用对象(通常是一个函数)作为线程的执行函数。可以通过直接传递函数名或使用 lambda 表达式来创建可调用对象,使用threading.Thread()创建
启动线程
创建 Thread 对象后,可以通过调用start()方法启动线程。
等待线程结束
如果需要等待一个线程执行结束,可以使用)join()方法。join() 方法会阻塞当前线程,直到被调用的线程执行结束。
以下为案例分析:
# 导入threading类 import threading import time def thread_job(): print('T1 start\n') for i in range(10): time.sleep(0.1) print('T1 finish\n') def T2_job(): print('T2 start\n') print('T2 finish\n') def main(): # 每个Thread对象都代表一个线程。每个线程我们可以让程序处理不同的任务,这样就是多线程编程。 # 将需要被调用的函数传递给参数target。 # name: 线程的名字。 # args=(): 使用args可以传入实参。 added_thread = threading.Thread(target=thread_job, name='T1') thread2 = threading.Thread(target=T2_job, name='T2') # 调用start方法来让线程启动。 added_thread.start() thread2.start() print('all done\n') if __name__ == '__main__': main()
输出:
T1 start
T2 start
all done
T2 finish
T1 finish
Tips:默认情况下,调用start方法使得线程开始后,并不需要等待该线程执行完毕就会往下执行,所以输出看起来没那么规则。
2-2、使用join()方法来阻塞进程
# 上边的代码几个线程是同时运行的,如果让一个先运行,一个后运行,应该怎么做呢? import threading import time def thread_job(): print('T1 start\n') for i in range(10): time.sleep(0.1) print('T1 finish\n') def T2_job(): print('T2 start\n') print('T2 finish\n') def main(): added_thread = threading.Thread(target=thread_job, name='T1') thread2 = threading.Thread(target=T2_job, name='T2') added_thread.start() # 调用join函数可以使得该线程结束后才会接着向下执行。 added_thread.join() thread2.start() thread2.join() print('all done\n') if __name__ == '__main__': main()
输出:
T1 start
T1 finish
T2 start
T2 finish
all done
Tips:这样看起来规则了,但是这和不使用线程直接执行函数好像没什么区别了。
2-3、其他threading模块常用方法
- threading.current_thread():
返回当前线程对象。 - threading.active_count():
返回当前线程总数,包括主线程和所有子线程。 - threading.enumerate():
返回一个包含所有当前活动线程的列表。 - threading.Lock():
创建一个锁对象,可以用来保护共享资源,防止多个线程同时访问。 - threading.RLock():
创建一个可重入锁对象,可以被同一个线程多次获取锁,主要用于递归函数。 - threading.Condition(lock=None):
创建一个条件变量对象,可以用来实现多个线程之间的协作。 - threading.Event():
创建一个事件对象,可以用来实现线程间的同步。 - threading.Timer(interval, function, args=[], kwargs={}):
创建一个定时器对象,用于在指定时间后执行某个函数。
三、GIL锁
3-1、什么是GIL?
GIL即全局解释器锁,每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行。
3-2、通过threading.Lock()保证线程同步
目的:为了协调各个线程修改同一份数据。
创建锁:lock = threading.Lock()
锁定和释放:lock.acquire()和lock.release()
参考文章:
Python多线程编程(一):threading 模块 Thread 类的用法详解.
Python 多线程编程(二):threading 模块中 Lock 类的用法详解.
Python多线程之threading.Thread()基本使用.
总结
Lock类这一块后续用的上的时候再研究吧,暂时还没用到,有需要的,看倒数第二篇参考文章噢。