我们从逻辑层面谈谈进程、线程与协程
01、什么是进程
进程是操作系统最基本的隔离单元、是一段运行在操作系统的代码。随着代码执行完毕而消失。 怎么理解上面这段话? 请看如下代码:
while (1) {
if (input(1)) {
break;
}
printf("正在运行")
sleep(1)
}
这是一段伪代码,这段代码成功编译后,最终会变成一个可执行程序。
如果这段程序没有接受到一个1的输入时, 这个程序会一直在运行。当我们一运行这个可执行程序时,进程就产生了。这个程序一旦接受到1输入时候,就意味着这段代码执行完毕,后面这个进程就被系统销毁了。
所以进程的本质就是:一段代码在系统上运行时的抽象叫法
。
02、什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
回到上面进程的定义, 我们会以为代码是由进程这个对象本身去执行的,其实并不是。真正执行代码的对象是线程,每个进程实例生成的同时也生成了一个线程实例,我们称这个线程为“进程的主线程”。
在现在多线程的时代, 线程使我们的程序更加的高效。
请看如下代码:
while (1) {
if (input(1)) {
break;
}
word = "默认"
dbword = db::getWord()
if dbword {
word = dbword
}
printf(word)
sleep(1)
}
上面伪代码做的事情是:通过db获取要打印的值。
在单进程,单线程模型下, 执行第7行代码的时候,如果db访问过慢整个执行过程是会被阻塞住。这样显然拖慢了整个程序的执行速度。
但是在单进程,多线程模式下这个问题就可以被解决。
我们修改一下代码:
while (1) {
if (input(1)) {
break;
}
word = "默认"
dbword = nil
thread.run(function(dbword){
dbword = db::getWord()
}(dbword));
if dbword {
word = dbword
}
printf(word)
sleep(1)
}
在这段代码里我们起了一个新的线程n去获取数据, 这样主线程就不会因为线程n而阻塞,所以我们可以称这段代码是可以并行的。也就提高了我们程序的效率。
在日常的开发中, 线程带给我们的好处还有很多, 只要我们确保我们的一段程序的各个功能块之间是可以并发执行的, 我们就可以使用多线程技术。
同时, 进程下面是可以拥有多个线程的, 属于同一个进程的多个线程共享进程的内存空间。如果不加锁控制,那么这样就会造成"竞态环境
"。竞态环境说的是一个变量的值被多个线程访问那么这个变量我们称之为不安全的, 我们不能用这个变量进行任何的判断,因为鬼知道代码并行的情况下变量被改成什么样了。
因为人脑天生就是单线程的,你不能像cpu一样同时去思考多件事情,所以以单线程去思考模式去思考多线程的东西的话就会很容易想岔。这也是多线程复杂度的来源。
通过上面的说明我们可以得出结论:线程是真正执行代码的个体,每个进程至少包含一个线程的同时又可以拥有多个线程
。
03、什么是协程
“协程”也称用户态线程,它与线程最本质的不同是协程是用户态的,线程是操作系统内核态的。
用大白话来讲,线程是操作系统提供最小调度单位, 协程是我们用代码实现的最小调度单位。
在日常应用中, 进程线程模型足够满足我们大部分的开发场景,但是在高性能网络服务器中,服务器要接受大量的请求与响应大量的请求,每一次请求都是一次io,而每一次io操作都有可能对应一个线程,fork一个线程消耗大约几MB的内存, 那么1000个大概就需要几个G。
协程的出现解决了这个问题,fork一个协程大约消耗几K到几十K内存。
大多数带有协程特性的语言并不是真正的协程。它们都只实现了协程的创建、执行权的切换,并没有实现协程同步、调度、互斥与通讯,协程的系统调用包装等。
世界只有2种语言完整的实现了完备的协程库它们分别是Go、Erlang。所以你应该明白go为什么被称为“云上的语言”了。
总结:协程是用户态的线程,它占用的资源更少,效率更高
。
04、思考
理解这3种模型有什么好处呢?首先并不是所有人一开始都在使用多线程模型进行开发、比如这些老牌的单线程单进程脚本语言javascript、php、python等。
理解它们的执行体的模型是怎么样的,我们也就知道它们的不足之处与它们的瓶颈在哪里。同时有助于我们去学习其他语言,方便我们去解决我们现在遇到的问题。也使我们能够做到快速的上手一门语言,也是我们经常挂在嘴边的“一通百通”。
每个语言的存在都它的道理,因为它们在某一时刻解决了某个问题。进程先存在之后到线程再到现在的协程,它们的出现都是为了提高生产力。
可以关注下公众号,里面还有例如mysql啊 数据结构等的技术文章