并发编程基本概念
并发和并行的区别:
并发:一段时间内,必须做完一些事情
并行:同一时刻,同时做几件事
并行是并发的一种水平扩展的解决手段。
进程和线程
进程是系统中最小的资源管理单位,是程序运行在内存中的实例。
线程是系统中最小的运算调度单位,包含在进程中,是实际干活的。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
一个标准的线程由线程ID,当前指令指针(PC)、寄存器集合和堆、栈组成。
在许多系统中,创建一个线程比创建一个进程快10-100倍。(并不代表线程速度快于进程)
线程基本概念
线程状态
状态 | 含义 |
就绪(Ready) | 线程能够运行,但在等待被调度。可能线程刚刚创建启动,或刚刚从阻塞中恢 复,或者被其他线程抢占 |
运行 (Running) | 线程正在运行 |
阻塞 (Blocked) | 线程等待外部事件发生而无法运行,如I/O操作 |
终止 (Terminated) | 线程完成,或退出,或被取消 |
- 就绪态:线程有资格被调度,有资格分配cpu时间
- 运行态:就绪态线程分配cpu时间后的状态,时间片用完后,线程就回到就绪态
- 阻塞态:线程运行过程中,如果需要等待 运行资源,则进入阻塞态
- 阻塞态线程如果条件满足,则回到 就绪态
- 终止态:线程运行中,被取消或者执行完成,进入终止态
- 阻塞态:线程运行过程中,如果需要等待 运行资源,则进入阻塞态
线程启动
运行程序会启动一个解释器进程,在这个进程中派生线程,线程共享一个解释器进程。
import threading def add(): print('add ~~~~~~~~~~') t1 = threading.Thread(target=add, name='add') # 创建一个线程实例 t1.start() # 启动线程
参数名 | 含义 |
target | 线程调用的对象,就是目标函数 |
name | 为线程起个名字 |
args | 为目标函数传递实参,元组 |
kwargs | 为目标函数关键字传参,字典 |
线程的工作就是执行 target 函数。
线程只能start一次,如果要再次start 需要重新实例化对象。
线程退出
Python没有提供线程退出的方法,线程在下面情况时退出
1、线程函数内语句执行完毕
2、线程函数中抛出未处理的异常
需要注意的是,线程虽然异常退出,但是结束的状态码依然是进程的状态码。
线程传参
因为线程就是执行target函数,所以线程的传参其实就是函数传参,通过args 和 kwargs传参,args传递的是元组,kwargs则是传字典。
import threading def add(x, y): print('add ~~~~~~~~~~{} + {} = {}'.format(x, y, x + y)) print('in add {}'.format(threading.get_ident())) # 在线程内,获取线程id t1 = threading.Thread(target=add, name='add', args=(3,), kwargs={'y': 4}) # 创建一个线程对称 t1.start() print(t1.ident, t1.name) # 获取线程的id, 名字
线程属性和方法
名称 | 含义 |
current_thread() | 返回当前线程对象 |
main_thread() | 返回主线程对象 |
active_count() | 当前处于alive状态的线程个数 |
enumerate() | 返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程 |
get_ident() | 返回当前线程的ID,非0整数 |
active_count、enumerate方法返回的值还包括主线程。
Thread实例的属性和方法
名称 | 含义 |
name | 只是一个名字,只是个标识,名称可以重名。getName()、setName()获取、设置这 个名词 |
ident | 线程ID,它是非0整数。线程启动后才会有ID,否则为None。线程退出,此ID依旧可 以访问。此ID可以重复使用 |
is_alive() | 返回线程是否活着 |
start() 和 run() 区别
需要注意的是:在解释器执行任何函数时,如果函数不返回,解释器不会继续向下执行。
主线程中通过start() 调用系统调用,启动一个工作线程后,并执行工作函数。satrt() 函数就返回了(此时start不会关心 工作线程是否执行完成),主线程也就不会阻塞,worker线程和主线程继续执行。
而执行run() 方法,并不会创建新的工作线程,它的所有执行都是在主线程上执行的。此时主线程必须等待 run方法执行完成并return后才能继续执行下去,也就是此时的主线程 阻塞 了。
start() 方法会创建一个新线程,并在这个新线程上,调用run()方法,run方法来执行 函数。
可以看到使用run方法后,没有创建新的线程,run方法依然是在主线程上运行的。所以执行run方法,会阻塞主线程运行。
start,run方法为什么只能执行一次分析
在主线程中,start() 和 run() 方法只能执行一次,如果要重新执行的话,需要在重新实例化线程对象,可以从start和run的源码中,查看怎么限制的只能执行一次?
start() 方法
在python37\Lib\threading.py 第847行,如果设置了self._started.is_set,就抛出异常,这里 的self就是实例化的线程对象。
run方法:
run方法是每次执行完成后就删除 target,args,kwargs属性,下次执行就无法传入target等属性,造成无法执行。
https://www.hugbg.com/archives/2738.html
评论