文艺一点的高并发:当我们在多线程的时候,我们在做什么


你好,我是编哥。今天我们来聊一聊线程并发的这部分内容。

为啥有并发这件事

首先,我们来了解一下为什么会出现多程序同时执行,也就是说,为什么会有并发这件事情。

没好处,谁去搞呢,肯定有一些促进的因素,

比如

1)是为了更好的资源利用

因为当程序有时候等待外部的输入输出的时候。他希望同时进行一些有价值的工作,然后在等待的时候会让出自己,让其他的程序进行运算。

2)公平,系统和社会都需要

第二点是为了公平

那时候,计算机是个宝贝,大学里面可能多个人,都在同时用一个宝贵的计算机,于是,会有很多个程序运行在同一个系统上。

为了让所有的这些用户的程序,都能分配到一定的时间让他们运行,来共享这个计算机的系统资源,于是需要一种公平的机制,比如说,小明你要在这里聊天,我cpu用3分钟处理你聊天的消息收发任务,小刚你要在这里看网页,好,我cpu给你2分钟处理下载网页内容的任务(当然,实际的时间比这个短多了,是毫秒级别的),两分钟后,cpu说,好的,现在我回来看看小明有没有新的消息需要我发送…

我们可以看到,相比单进程那个远古时代下,你结束一个进程之后再开始下一个进程,多进程这种方式更可取。

而进程多了,就好比人多了,一定需要有一个公平的准则,才能让系统运行良好,这就引出了时间片轮转,cpu进程调度的知识了,想看的朋友留言,我以后开个系列给大家讲讲

3)方便,专业的事,交给专业的人

第三个点可能就是为了方便的目的

让一个程序去完成一个单独的任务,然后只要让这些程序之间相互协调,我们就能完成更加复杂的事情,Linux的哲学之一就是如此

试想一下,不怕麻烦的你,编写一个庞大的程序,然后在里面塞满各种冗余的,多种任务的处理,揉合成一块,然后不跟其他程序的发生交互,自己的事情全干完,累的要死。

相比来说,另一些家伙,秉着专业的事交给专业的人去做的思维,他们各自关注自己的能力,设计好自己该做的部分。然后很多其他的人的程序,可以调用我这个程序进程的某个函数,而我也可以复用别人的劳动成果,这样一来,就会更方便,大家都实现了自己的价值,生活也更容易了

小小总结一下

就是因为这3个关注点

1)资源的利用

2)为了公平的让每个程序跑一段时间

3)然后还有方便这个目的

促进了进程的发展,也就是出现了多进程,同样的,这个原因也促进了多线程的这个发展

一个进程,实际上就是一个程序,是一条分支,就这样走下去了

但是多线程是,现在在这个程序进程里面。我们可以看到,多条分支同时在进行齐头并进,然后这个多个分支,他们共享这一个进程内的所有的资源:

1)比如说系统分配的内存空间

2)比如说他们都可以使用某一些文件

而线程所不共享的就是:

1)每个线程会有自己的计数器(指令执行到哪里啦)

2)有自己的本地变量

3)有自己的栈

同时,线程的出现,其实也适配了这种硬件的发展迭代,因为我们现在的硬件就是多处理器的,所以一个程序(进程),如果有多个控制流,也就是说我有多个线程在其中,那这一个程序的多个线程,正好可以在多cpu的情况下被调度,某个线程就在某个cpu上面去运行。

所以线程有时候会被称为 轻量级进程,你肯定会在一些其他地方听到人们这样说

而且大多数现代的操作系统都很自然的,把线程,当做在时间上去调度的一个基本单元,而不是进程

进程往往是这个现代的操作系统对资源分配管理的一个基本的,最基础的一个单位。

像之前说的,因为所有的线程,寄宿在同一个进程中,那他们可以访问相同的变量,因为他们共享这个进程所被系统分配的内存(内存就是变量的大池子)

其实也就是我们java中通常会说的堆空间,我们可以在堆上来分配对象,然后线程们共享这些对象。

这就相对于比较古老的进程间通讯:ipc(inter-process communication)这种机制,在数据共享方面来得更好一些

但是如果没有很明确的同步的机制去管理这些共享的数据,比如说堆上的对象,其中一个线程可能会修改到其他线程正在使用的对象数据,这样的话就会容易产生意外的错误,这就是多线程的一个缺点吧

这个,我们之后会说

线程的优点

说一下线程的优点吧

其实简单的来说就分成四点,第一点就是提高性能。第二点就是对于用户界面图形编程这些我们平常会遇到的这些东西会有帮助。然后第三点,就是适配了多处理器,刚刚也说到了,他跟匹配了现在硬件的这个发展。最后一点第四点就是多线程其实简化了我们的编程的模型。

第四点啥意思呢?这里的意思是,让一个程序的分工成为可能。

像我们人类社会有专业的分工,电工做电工的事,程序员做程序员的,美术生去画用户界面。

如果按照时序,面向过程,我需要先找到电工铺设好电线,然后才再去培养一个程序员,然后再去培养一个美术的学生去画出用户的界面

而多线程实际上就是跟我们人类社会一样,以人为本,面向市场需求,进行各自的分工,然后在合适的时候把这些分开的这些线程,嗯,或者说人类分工好的角色组织起来。然后其实这就得到了一个简化了的社会模型,

同时,来看看我们所编写的软件,它也很像一个社会一样,就是以各种的线程作为单位,组成一个简洁的运行模型。

线程的缺点

接着,我们来说多线程,或者说并发这种模型的缺点吧

安全性

首当其冲的第一个缺点就是安全性

你会在别的地方会听到一个专业术语,就是叫竞争条件,或者说竞态条件。英文叫做race condition

说了这么久,看一段代码吧

class UnsafeRun {
    private int value;
    public int getNext() {
        return value++;
    }
}
// create a instance 
UnsafeRun example = new UnsafeRun();
// 这是一个线程: t1
new Thread(() -> {
    int i = example.getNext();
}).start();
// 这是一个线程: t2
new Thread(() -> {
    int j = example.getNext();
})
// 可能的危险情况:
// i = 4
// j 也 = 4

图示:

并发导致的危险情况

所以总结下来这个 race condition 就是当我们程序员在编码的时候不能很放心的保证这段代码,当未来在某个地方运行的时候,它不能像我们想象的那样,如我们所愿。

它很有可能产生不确定或者说模糊的行为的话,那这个时候就会是一个安全性方面的一个缺点

活性

第二个危险就是活跃性(活性)的一个危险,如果你了解死锁的话,活跃性就是来表述这个情况

死锁是什么呢?就是两个进程a和b,a在等待b去释放某一个资源,但是同时b也在等待a来释放一个资源,这就好比两个人不让步,拿着对方需要的东西,大家都不肯往后退

这个时候,如果没有其他人来劝架,这俩人就只能在那儿干等着,互相在这儿干瞪眼,没办法继续往下,去完成自己的事情

性能

第三个缺点就是性能上面的一个损失

因为在对cpu来说,他在执行这些程序的时候,其中有一个任务就是要去切换上下文

上下文这种东西,如果简单来说,其实就是记忆

我们人在接到下一个任务的时候,要把上一个任务做到哪里了,给记录下来,为了以后继续要拾起来这份工作的时候,我cpu应该继续往下做哪件事,哪一个动作?

cpu要做这样的事情,就叫做保存现场,就是这个线程当前运行到哪个状态了?我要把它的状态保存下来

下次我cpu要是继续挑一些线程回来运行,正好挑到这个线程了,上一次被停止的时候他的状态什么样,然后接着在这个,上次被停止的状态继续往下干活。这就是上下文切换的意思

那么我们就可以知道,如果cpu如果大部分时间,都花费在这种保存现场然后恢复现场这种工作上,那它相比来说,就会有更少的时间去真正的去处理那个我们要做的真实的任务了

因为你一直在去保存现场和恢复现场的事情,那现场的事儿有多少时间去处理呢?

就此打住

好了,这篇文章就是简单的去概括了一下线程为何出现,以及其优缺点。

因为编哥觉得,当我们写一些Java线程,并发的代码的时候,我们可能不知道为什么我们要写并发的代码,以及背后可能的坑在哪里

那这篇文章就是要告诉大家其实多线程的出现并非是空穴来风,纯粹玩一个榨干机器算力的技巧,它实际上有这种长久的历史原因,有各种技术的演进,软件工程,设计模式的发展,然后以及各种系统性能啊,硬件方面的考量一起推动了多线程的出现

希望这篇文章会对我们以后进行多线程编程的时候,有更深刻的思考,有一个更加高的视角去明白我们正在干的事情是什么,以及要不要这么干,里面会有哪些优劣取舍要考虑。

接下来是更加具体的,关于多线程安全的文章,以及一些实际案例,等编哥为你娓娓道来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值