了解多任务的概念
多任务是指同一时间内执行多个任务,多任务的最大好处就是充分利用CPU资源,提高程序的执行效率
多任务的执行方式
①并发在一段时间内交替去执行任务。
②对于单核CPU处理多任务,操作系统轮流让各个软件交替执行,比如让软件1执行0.01秒,切换到软件2,软件2执行0.01秒在切换到软件3…这样反复执行下去。表面上看感觉这些软件都在同时执行,但实际上每个软件都是交替执行的,由于CPU的执行速度实在太快了我们感觉这样软件都在同时执行一样。这里注意单核CPU是并发执行多任务的。
③并行
对于多核CPU处理多任务,操作系统会给每个CPU的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核CPU是并行的执行多任务,始终有多个软件一起执行
注意;CPU是硬件,操作系统是用来操作硬件的
进程
进程的介绍:在python程序中,想要实现多任务可以使用进程完成,进程是实现多任务的一种
进程的概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源。一个程序没有运行起来就是一些程序代码,存放在磁盘里,比如QQ,一旦运行起来CPU就会分配资源(CPU资源,内存资源,网络资源…)
举个栗子:现实生活中的公司可以理解成一个进程,公司提供办公资源(电脑,办公桌椅这些都是资源)真正干活的是员工,员工可以理解成线程。
import os
import time
def run():
print('跑进程pid:',os.getpid())
for i in range(3):
print('跑...')
time.sleep(1)
def jump():
print('跳进程pid:',os.getpid())
for i in range(3):
print('跳...')
time.sleep(1)
if __name__ == '__main__':
os.getpid()
#使用进程实例化进程对象
run_process = multiprocessing.Process(target=run,name='run_process')
junp_process = multiprocessing.Process(target=jump,name='jump_process')
#启动进程
run_process.start()
junp_process.start()
运行结果:
跑进程pid: 33084
跑...
跳进程pid: 20580
跳...
跑...
跳...
跑...
跳...
获取进程编号的目的
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的
获取进程编号的两种操作
①获取当前进程编号
②获取当前父进程编号
os.getpid() #获取当前进程编号
os.getppid() #获取当前父进程编号
import os
import time
def run():
print('跑父进程pid:%s' % os.getppid())
for i in range(3):
print('跑...')
time.sleep(1)
def jump():
print('跳父进程pid:%s' % os.getppid())
for i in range(3):
print('跳...')
time.sleep(1)
if __name__ == '__main__':
#获取主进程oid
print('主进程编号:',os.getpid())
#使用进程实例化进程对象
run_process = multiprocessing.Process(target=run,name='run_process')
junp_process = multiprocessing.Process(target=jump,name='jump_process')
#启动进程
run_process.start()
junp_process.start()
运行结果
主进程编号: 25092
跑父进程pid:25092
跑...
跳父进程pid:25092
跳...
跳...跑...
跳...
跑...
当然我们获取进程标号后还可以在适当的位置杀死该进程
import multiprocessing
import os
import time
def run():
print('跑父进程pid:%s' % os.getpid())
for i in range(3):
print('跑...')
time.sleep(1)
# 运行到此处杀死进程
os.kill(os.getpid(),9)
def jump():
print('跳父进程pid:%s' % os.getpid())
for i in range(3):
print('跳...')
time.sleep(1)
if __name__ == '__main__':
#获取主进程oid
print('主进程编号:',os.getpid())
#使用进程实例化进程对象
run_process = multiprocessing.Process(target=run,name='run_process')
junp_process = multiprocessing.Process(target=jump,name='jump_process')
#启动进程
run_process.start()
junp_process.start()
运行结果:
主进程编号: 33976
跑父进程pid:13736
跑...
跳父进程pid:31200
跳...
跳...
跳...
由上面的运行结果可看出跑子进程仅运行了一次,因为在运行了以一次后就被杀死了
进程带有参数的任务
假如我们使用进程执行的任务有参数,如何给函数传参呢?
Process类执行任务并给任务传参有两种方式
①args表示以元组的方式给执行的任务传参
②kwargs 表示以字典的方式给执行的任务传参
def run(count):
for i in range(count):
print('跑')
time.sleep(1)
def jump(count):
for i in range(count):
print('跳')
time.sleep(1)
if __name__ == '__main__':
# 创建进程对象时可以给进程传参数
#args 以元组的方式,一个参数加逗号
# kwargs以字典的方式
sing_process = multiprocessing.Process(target=sing,args=(4,))
dance_process=multiprocessing.Process(target=dance,kwargs={'count':4})
run.start()
jump.start()
进程的注意点介绍
①进程之间不共享全局变量
②主进程会等待所有子进程执行结束再结束
创建的子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以子进程之间不共享全局变量是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已
# 定义全局变量
g_list = list()
# 添加数据的任务
def add_data():
for i in range(5):
g_list.append(i)
print("add:", i)
time.sleep(0.2)
# 代码执行到此,说明数据添加完成
print("add_data:", g_list)
def read_data():
print("read_data", g_list)
if __name__ == '__main__':
# 创建添加数据的子进程
add_data_process = multiprocessing.Process(target=add_data)
# 创建读取数据的子进程
read_data_process = multiprocessing.Process(target=read_data)
# 启动子进程执行对应的任务
add_data_process.start()
# 主进程等待添加数据的子进程执行完成以后程序再继续往下执行,读取数据
add_data_process.join()
# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
# read_data_process.daemon = True 使用要守护主进程的子进程对象
read_data_process.start()
print("main:", g_list)
运行结果:
add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []
为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程执行
①设置守护主进程的方式:子进程对象.daemon=True
②销毁子进程方式:子进程对象.terminate()
线程
在python中想要实现多任务除了使用进程还可以使用线程来完成,线程是实现多任务的另外一种方式
线程的概念
线程是进程中执行代码的一个分支,每个执行分支(线程)想要工作执行代码需要CPU进行调度,也就是说线程是CPU调度的基本单位,每个进程至少有一个线程,而这个线程就是我们常说的主线程。
线程的作用
import threading
import time
# 唱歌任务
def sing():
# 扩展: 获取当前线程
print("sing当前执行的线程为:", threading.current_thread())
for i in range(3):
print("正在唱歌...%d" % i)
time.sleep(1)
# 跳舞任务
def dance():
# 扩展: 获取当前线程
print("dance当前执行的线程为:", threading.current_thread())
for i in range(3):
print("正在跳舞...%d" % i)
time.sleep(1)
if __name__ == '__main__':
# 扩展: 获取当前线程
print("当前执行的线程为:", threading.current_thread())
# 创建唱歌的线程
# target: 线程执行的函数名
sing_thread = threading.Thread(target=sing)
# 创建跳舞的线程
dance_thread = threading.Thread(target=dance)
# 开启线程
sing_thread.start()
dance_thread.start()
当前执行的线程为: <_MainThread(MainThread, started 13360)>
sing当前执行的线程为: <Thread(Thread-1, started 13168)>
正在唱歌...0
dance当前执行的线程为: <Thread(Thread-2, started 26840)>
正在跳舞...0
正在跳舞...1
正在唱歌...1
正在唱歌...2
正在跳舞...2
线程执行任务并传参有两种方式:
- 元组方式传参(args) :元组方式传参一定要和参数的顺序保持一致。
- 字典方式传参(kwargs):字典方式传参字典中的key一定要和参数名保持一致。
def dance():
time.sleep(1)
print(threading.current_thread().name)
if __name__ == '__main__':
for _ in range(4):
dance_thread = threading.Thread(target=dance)
dance_thread.start()
# dance_thread.join() 线程等待,会执行完一个线程再执行下一个线程,这样就会变的有序
执行结果:
当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3
说明:
- 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
- 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
主线程会等待所有子线程执行结束再结束
import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
sub_thread = threading.Thread(target=show_info)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")
test: 0
test: 1
over
test: 2
test: 3
test: 4
设置守护主线程,执行结果
test: 0
test: 1
over
通过上面代码的执行结果,我们可以得知: 主线程会等待所有的子线程执行结束再结束
假如我们就让主线程执行1秒钟,子线程就销毁不再执行,那怎么办呢?
- 我们可以设置守护主线程
守护主线程:
- 守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
- threading.Thread(target=show_info, daemon=True)
- 线程对象.setDaemon(True)
注:线程之间共享全局变量,因为进程是资源分配的基本单位,而进程可以创建多个线程,也就是说线程是i依附在进程的,用的都是进程的同一份资源,所以共享全局变量
线程之间共享全局变量数据出现错误问题
import threading
# 定义全局变量
g_num = 0
# 循环一次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环一次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 启动线程
second_thread.start()
执行结果:
sum1: 1210949
sum2: 1496035
多线程同时对全局变量进行操作数据发生了错误
总结:共享全局变量出错会出现资源竞争,两个线程都想对同一个全局变量进行操作,但是操作的时候不是一次性完成的,分为三步:先读取再修改然后再写入,中间不能被打断,打断了的话就是先读取后在修改的操作时没有执行完而被打断,另一个线程此时要执行相同的操作会覆盖前一个,以此循环
全局变量数据错误的解决办法
线程同步:保证同一时刻只能有一个线程去操作全局变量,按预定的先后次序进行运行。
示例
import threading
# 定义全局变量
g_num = 0
# 循环1000000次每次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环1000000次每次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
# 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行,但是需要注意的是,线程等待之后就是单任务了
first_thread.join()
# 启动线程
second_thread.start()
. 小结
- 线程执行执行是无序的
- 主线程默认会等待所有子线程执行结束再结束,设置守护主线程的目的是主线程退出子线程销毁。
- 线程之间共享全局变量,好处是可以对全局变量的数据进行共享。
- 线程之间共享全局变量可能会导致数据出现错误问题,可以使用线程同步方式来解决这个问题。
- 线程等待(join)
互斥锁的概念
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作
- 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等线程执行完释放锁后,其他等待的线程再去抢这个锁
import threading
# 定义全局变量
g_num = 0
# 创建全局互斥锁
lock = threading.Lock()
# 循环一次给全局变量加1
def sum_num1():
# 上锁
lock.acquire() 上锁和解锁之间的关键代码只能有一个线程去运行
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 释放锁
lock.release()
# 循环一次给全局变量加1
def sum_num2():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
# 释放锁
lock.release()
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
second_thread.start()
# 提示:加上互斥锁,哪个线程抢到这个锁我们决定不了,哪个线程抢到锁那个线程先执行,没有抢到的线程需要等待
# 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行
执行结果:
sum1: 1000000
sum2: 2000000
-
说明:
通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题
-
acquire和release方法之间的代码同一时刻只能有一个线程去操作
-
如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
小结
- 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
- 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
- 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
- 互斥锁如果没有使用好容易出现死锁的情况
1. 死锁的概念
死锁: 一直等待对方释放锁的情景就是死锁
死锁的结果
- 会造成应用程序的停止响应,不能再处理其它任务了。
避免死锁解决方法:上锁的代码在结束时一定要解锁
进程和线程的对比的三个方向
- 关系对比
- 区别对比
- 优缺点对比
1. 关系对比
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程。
2. 区别对比
- 进程之间不共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依存在进程中
- 多进程开发比单进程多线程开发稳定性要强 因为线程会共享全局变量,加锁的部分没有释放锁,会死锁这样其他线程就没法执行了
3. 优缺点对比
- 进程优缺点:
- 优点:可以用多核
- 缺点:资源开销大 开一个进程会分配很多资源,比如网络资源,内存空间,磁盘空间,信号资源 最大10M
- 线程优缺点:
- 优点:资源开销小 主要的开销就是内存栈空间,也就是局部变量空间,因为外部的资源都是共享进程的 大概512k
- 缺点:不能使用多核
协程
协程时python中另一种实现多任务的方式
只不过比线程占更小执行单元(理解为需要的资源)。
拿线程和协程相比较理解:线程是系统级别的,它是由CPU调度;协程是程序级别的,由程序员根据自己的需求调度。我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以终端去执行别的子程序;别的子程序也可以中断来继续执行之前的程序,这就是协程。
**比较专业的理解是:**协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
Python中如何实现协程?
2.1yield实现协程
import time
def sing():
while True:
print('唱歌。。。')
# 可以暂停函数,在这个位置暂停函数,切换到next
yield
def dance():
while True:
print('跳舞。。。')
yield
if __name__ == '__main__':
sing_x = sing()
dance_x = dance()
while True:
# 可以自定义函数运行的顺序
next(sing_x)
next(dance_x)
time.sleep(1)
通俗理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换另外一个函数中执行,注意不是通过调用的方式执行的,并且切换的此处以及什么时候切换到原来的函数都是由开发者自己确定的
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:1 2 x y 3 z。
协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;
不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
当遇到网络卡的时候会自动切换到其他任务,因为IO操作时非常耗时的
2.2 gevent 实现协程
gevent会主动识别程序内部的IO操作,当子程序遇到IO后,切换到别的子程序。如果所有的子程序都进入IO,则阻塞。
import gevent
def f(n):
for i in range(n):
#返回当前正在执行的greenlet
print(gevent.getcurrent(),i)
# 模拟一个耗时操作
gevent.sleep(1)
#创建一个普通的Greenlet对象并切换
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
<Greenlet at 0x7f09b3cb2c48: f(5)> 0
<Greenlet at 0x7f09b35ab048: f(5)> 0
<Greenlet at 0x7f09b35ab148: f(5)> 0
<Greenlet at 0x7f09b3cb2c48: f(5)> 1
<Greenlet at 0x7f09b35ab048: f(5)> 1
<Greenlet at 0x7f09b35ab148: f(5)> 1
<Greenlet at 0x7f09b3cb2c48: f(5)> 2
<Greenlet at 0x7f09b35ab048: f(5)> 2
<Greenlet at 0x7f09b35ab148: f(5)> 2
<Greenlet at 0x7f09b3cb2c48: f(5)> 3
<Greenlet at 0x7f09b35ab048: f(5)> 3
<Greenlet at 0x7f09b35ab148: f(5)> 3
<Greenlet at 0x7f09b3cb2c48: f(5)> 4
<Greenlet at 0x7f09b35ab048: f(5)> 4
<Greenlet at 0x7f09b35ab148: f(5)> 4
import gevent
import time
from gevent import monkey
def run(count):
for i in range(count):
print('跑...')
time.sleep(1)
def jump(count):
for i in range(count):
print('跳...')
time.sleep(1)
if __name__ == '__main__':
# 此处主线程创建了两个协程对象
g1 = gevent.spawn(run, 3)
g2 = gevent.spawn(jump, 3)
# join 主线程等待协程g1执行完成再继续,遇到io耗时会执行协程内的任务
g1.join()
# join 主线程等待协程g2执行完成再继续
g2.join()
跑...
跑...
跑...
跳...
跳...
跳...
time.sleep,这是python 原生的耗时方法,不能被gevent所识别但是可能之前写好的代码就是python原生的好事方法比如用的time.sleep
要解决这个问题可以给程序打补丁(猴子补丁),也就是让gevent能识别系统的io耗时
import gevent
import time
from gevent import monkey
monkey.patch_all()
def run(count):
for i in range(count):
print('跑...')
time.sleep(1)
def jump(count):
for i in range(count):
print('跳...')
time.sleep(1)
if __name__ == '__main__':
# 此处主线程创建了两个协程对象
g1 = gevent.spawn(run, 3)
g2 = gevent.spawn(jump, 3)
# join 主线程等待协程g1执行完成再继续,遇到io耗时会执行协程内的任务
g1.join()
# join 主线程等待协程g2执行完成再继续
g2.join()
进入到补丁里面
为TURE都是系统识别的耗时任务
注释:必须给协程加耗时才能让任务交替执行,来回切换,要gevent能识别的耗时才行,如果不是则需要打补丁
2.2 greenlet实现协程
from greenlet import greenlet
# greenlet 其实就是手动切换;gevent是对greenlet的封装,可以实现自动切换
def test1():
print("123")
gr2.switch() # 切换去执行test2
print("456")
gr2.switch() # 切换回test2之前执行到的位置,接着执行
def test2():
print("789")
gr1.switch() # 切换回test1之前执行到的位置,接着执行
print("666")
gr1 = greenlet(test1) # 启动一个协程 注意test1不要加()
gr2 = greenlet(test2) #
gr1.switch()
协程与线程的区别:
-
一个线程可以多个协程,一个进程也可以单独拥有多个协程。
-
线程进程都是同步机制,而协程则是异步。
-
协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
4)线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
5)协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。
6)线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。
程序与进程的区别:
1)程序就是编写的代码是存在硬盘上的,它没有运行是死的,说白了就是编写的代码没运行就是程序
2)程序不能单独运行,只有将程序加载到内存中,系统为它分配了资源才能运行,而这种执行的程序就是进程
区别:程序是指令的集合,它是进程运行的静态描述。进程是程序的一次执行活动,属于动态的概念。
在多道编程中,允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发执行。这样的设计大大提高了CPU的使用率(进程其实就是为了CPU实现多道编程提出的)
有了进程为什么还要线程?
- 进程在同一时间只能干一件事,如果想同时做两件事或多件事进程就无能为力了
- 进程在执行的过程中如果阻塞。例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据也将无法执行。
比如,我们在使用QQ聊天,QQ作为一个独立的进程如果同一时间只能干一件事,那如何实现在同一时刻既能监听键盘输入,又能监听其他人给你发消息,同时还能把别人发的消息显示在屏幕上呢?程序中有多个功能,想让这些功能并行所以就出现了线程,让进程中有多个功能并行执行。相当于每个车间都至少有一个工作的人,这个人就是线程也叫主线程,它被包含在进程之中,是进程中实际的运作单位
协程,又称微线程。一句话说明什么是协程:协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈(比如保存了函数中的函数体和局部变量之类的)。协程之间实现并发也是要不断切换的,只不过这种切换不在cpu级别上发生的,而是在程序内部由程序员自己控制的。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候恢复先前保存的寄存器上下文和栈,因此:协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
协程的优点:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
1)所谓原子操作是指不会被线程调度机制打断(分时机制在不同线程之间切换,运行一段时间强制切换)的操作;这种操作一旦开始就一直运行到结束。要么成功要么失败。原子操作可以是一个步骤也可以是多个操作步骤,但是其顺序是不可以被打乱,或者只执行部分。协程就是在一个线程里面不涉及线程的切换 - 高并发+高扩展+高成本:一个CPU支持上万的协程
协程的缺点:
- 无法利用多核(一个线程只能在一个核上跑),协程的本质就是个单线程,它不能同时将单个CPU在多个核上用,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应该都没这个必要,除非是CPU密集型应用
什么条件能称为协程:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其他协程