云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

多线程和高并发介绍—基本概念《并行概念总结》

jxf315 2025-04-24 01:59:03 教程文章 30 ℃

前言

总结送视频学习资料(包含视频、技术学习路线图谱、文档等)

本文主要是针对多线程和高并发的概念做了简单的描述,介绍了什么是多线程,什么是高并发,并且对多线程和高并发的关系做了比较描述。

一、什么是多线程?

1.多线程介绍

什么是多线程,首先看下百度百科对多线程的定义;多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。

2.多线程实现原理?

实现多线程是采用一种并发执行机制。 并发执行机制的原理: 简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果 。 多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。这就是多线程程序。

3.白话文解释多线程

举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。 按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。 如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。

4.多线程存在的问题

1.多线程存在线程安全问题2.死锁问题

二、什么是高并发?

1.高并发介绍

首先看下百度百科对高并发的定义;高并发(High Concurrency)通常是指通过设计保证系统能够同时并行处理很多请求。通俗来讲,高并发是指在同一个时间点,有很多用户同时的访问同一 API 接口或者 Url 地址。它经常会发生在有大活跃用户量,用户高聚集的业务场景中。

多线程与高并发基本概念

1.同步(Synchronous)与异步(Asynchronous)

同步和异步通常形容一次方法的调用。同步方法调用开始后调用者必须等到方法调用返回才能进行后续行为。异步方法则像一个消息的传递,调用方法后立即返回而方法体则在后台继续运行,调用者无需等待继续后续操作。


2.并发(Concurrency)和并行(Parallelism)

并发和并行都能表示两个或多个任务一起执行,但是并发偏重于任务交替执行,多个任务之间很可能是串行的,而并行是真正意义上的"同时执行"。


严格的来说并行的多个任务是真实的同时执行,而并发是交替的,有系统在任务之间进行切换,即多个任务之间是串行并发,但对人来说会造成多任务是并行的错觉。

3.临界区

临界区用来表示一种公共资源或共享数据,可以被多个线程使用,但是一次只能有一个线程使用,一旦临界区资源被占用其他线程如果想要使用这个资源就必须等待。

4.阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用来形容多线程的互相影响。当一个线程占用了临界区资源,那么其他需要这个资源的线程必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。非阻塞与之相反,它强调没有一个线程可以妨碍其他线程执行,所有线程都会不断尝试向前执行。

5.死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)

死锁、饥饿和活锁都属于多线程活跃性问题。

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

饥饿是指某一个或多个线程因为优先级等原因导致其无法获得所需要的资源,最终导致一直无法执行。

活锁则是线程之间一直将资源释放给其他线程,这会使资源不断在两个线程中跳动,而没有一个线程可以同时拿到所有资源而正常执行,这种情况就是活锁。

并发级别

由于临界区的存在,多线程之间的并发必须受到控制,根据并发的策略大致将并发的级别进行分为阻塞,无饥饿,无障碍,无锁,无等待几种。

1.阻塞(Blocking)

一个线程是阻塞的那么在其他线程释放资源前,当前线程无法继续执行。当使用synchonized关键字或者重入锁时,得到的就是阻塞的线程。

无论是synchonized关键字或者重入锁都会试图在执行后续代码前得到临界区的锁,如果得不到则会挂起等待,直到占有了所需要的资源为止。

2.无饥饿(Starvation-Free)

如果线程之间有优先级,那么在线程调度的时候总会倾向于高优先级的线程。如果没有优先级,满足先来后到,则不会产生饥饿。

3.无障碍(Obstruction-Free)

无障碍是最弱的的非阻塞调度。两个线程如果都是无障碍的执行,那么他们不会因为临界区的问题导致一方挂起。如果阻塞的控制方式是悲观策略,那么相对来说非阻塞的调度是一种乐观的策略。一种可行的无障碍实现是以一个“一致性标记”来实现。线程在操作前先读取并保存这个标记,在操作完成后再读取检查这个标记是否被更改如果是一致的则表示资源没有访问冲天,否则表示有其他线程对此资源有过修改需要重试操作。而任何对资源有修改的线程在更改数据前需要更新这个一致性标记,表示数据不安全。

4.无锁(Lock-Free)

无所谓的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区的访问,但是无锁的并发保证必然有一个线程能在有限步内完成操作离开临界区。(但是有可能总是不成功会出现类似饥饿的现象)

在无锁的调用中,典型的特点是包含一个无穷循环,在这个循环中线程会不断尝试修改共享变量。如果没有冲突修改成功则程序退出,否则继续尝试修改。

5.无等待(Wait-Free)

无锁是在无锁的基础上更进一步进行扩充,它要求所有线程都必须在有限时间内完成,这样就不会引起饥饿问题。

典型的无等待结构就是RCU(Rrad-Copy-Update)。它的基本思想是对数据的读不进行控制,所以所有的读线程都是无等待。但是写数据是会先取得源数据的副本,然后只修改副本数据,修改完成后在合适的时机回写数据。

有关并行的两个重要定律

1.Amdahl定律

加速比定义:加速比=优化前系统耗时/优化后系统耗时


根据Amdahl定律使用多核CPU对系统进行优化,优化的效果取决于CPU的熟练以及串行化程序的比重,CPU越多,串行化比重越低,则优化效果越好。仅提高CPU数量而不降低程序的串行化比重也无法提高系统性能。

2.Gustafson定律


如果串行化比例小,并行化比例大,那么加速比则是处理器个数。只要不断累加处理器就能更快。

Amdahl强调的是:当串行化比例一定时,加速比是有上限的,不管堆叠多少个CPU进行计算都不能突破上限。

Gustafson关心的是:如果可悲 并行化代码比重足够多,那么加速比能随着CPU的数量线性增长。

这两个定律并不矛盾,从极端角度来看,如果系统中没有可被串行化代码(F=1)那么对于两个定律来说加速比都是1。反之两个定律得到的加速比为n(处理器个数)。


1.原子性(Atomicity)

原子性是指一个操作不可中断。即使是多个线程一起执行的时候,一个操作一旦开始就不会被其它线程干扰。

如赋值操作int i=10;但是在32位机中的long类型不能保证原子性。

2.可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其它线程是否能立即知道这个修改。在串行程序中可见性问题不存在,因为在任何一个操作中修改了某个变量那么在后续的步骤中读取这个变量的值一定是修改后的新值。

3.有序性(Ordering)

有序性问题的原因是因为程序在执行时会进行指令重排,重排后的指令与原指令的顺序未必一致。指令重排序在串行代码中能保证语义一致,但在多线程中并不保证。

4.那些指令不能重排:Happen-Before规则

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量地写,先发生于读,这保证了volatile变量的可见性

锁规则:解锁必然发生在随后的加锁前

传递性:A先于B,B先于C,则A先必然于C

线程的start()方法先于它的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行,先于finalize()方法

总结;

在CPU比较繁忙,资源不足的时候(开启了很多进程),操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。

详细教程资料关注+后台私信;资料;两个字可以免费视频领取+文档+各大厂面试题 资料内容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,嵌入式 等。

最近发表
标签列表