编程语言
首页 > 编程语言> > 程序员的自我修养 - 基础篇(3)线程安全

程序员的自我修养 - 基础篇(3)线程安全

作者:互联网

文章目录

竞争与原子操作

在这里插入图片描述在这里插入图片描述

  很明显,自增(++)操作在多线程环境下会出现错误是因为这个操作在被编译为汇编代码后不止一条指令,因此在执行的时候可能执行了一半就被调度系统打断,去执行别的代码。我们把单指令的操作称为原子的(Atomic),因为无论如何,单条指令的执行是不会被打断的。为了避免出错,很多体系结构提供了一些常用的原子指令。

同步与锁

  原子操作比较适用于简单特定的场合,在复杂场合下,比如我们要保证修改一个复杂数据结构的原子性,原子操作指令就力不从心了。

  对数据访问的同步(synchronization),即指在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。如此,对数据的访问就被原子化了。

  二元信号量(Binary semaphore) 是最简单的一种锁。它只有两种状态:占用和非占用,它适合只能被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量的线程会获得该锁,并将二元信号量置为占用状态,此后所有试图获取该二元信号量的线程都会等待,直到锁被释放。

  对于允许多个线程并发访问的资源,多元信号量简称信号量(Semaphore),是一个很好的选择。一个初始值为N的信号量允许N个线程并发访问。线程访问资源的时候先获取该信号量,进行如下操作:
  将信号量的值减1;
  如果信号量的值小于0,则进入等待状态,否则继续执行;
  访问完资源后,线程释放信号量,进行如下操作:
  将信号量加1;
  如果信号量的值小于1,唤醒一个等待中的线程。

  互斥量(mutex) 和二元信号很类似,资源仅同时运行一个线程访问。但不同的是,信号量在整个系统被任意线程获取并释放,也就是说信号量可以在一个线程中被获取并被另一个线程释放。而互斥量要求哪个线程获取了它,就必须要由该线程负责释放它。

  临界区(critical section) 是比互斥量更加严格的同步手段。在术语中,把临界区的锁的获取,称作是进入临界区,而把锁的释放称作是离开临界区。临界区与信号量、互斥锁的区别在于,互斥量和信号量在系统的任何进程里是可见的,也就是说,一个进程创建了互斥量和信号量,另一个进程试图去获取该锁是合法的。然而,临界区的作用范围仅限于本进程,其他进程无法获取该锁。除此之外,临界区具有和锁相同的性质。

  读写锁(read - write lock) 致力于一种更加特定的场合的同步。对于一段数据,多个线程同时读取总是没问题,但假设操作不是原子型的,只要有任何一个线程试图对这个数据进行修改,就必须使用同步手段来避免出错。对于读取频繁,偶尔写入的大量数据访问,使用信号量、互斥锁会显得非常低效。读写锁可以规避这个问题。读写锁有两种获取方式,共享的(shared)和独占的(exclusive)。当锁处于自由状态时,试图以任何一种方式获取都能成功,并将锁置于对应的状态。如果锁处于共享状态,其他线程以共享的方式获取锁仍然会成功,此时这个锁分配给了多个线程。然而,如果其他线程试图以独占的方式获取已经处于共享状态的锁,那么它必须等待锁被所有的线程释放。相应地,处于独占状态的锁阻止任何其他线程获取该锁。
在这里插入图片描述

多线程的内部情况

  三种线程模型
  线程的并发执行是由多处理器和操作系统的调度来实现的。但实际情况要更为复杂一些;大多数操作系统,包括windows和linux,都在内核里提供线程的支持,即内核线程。然而用户实际使用的线程并不是内核线程,而是存在于用户态的用户线程。用户态线程并不一定在操作系统内核里对应同等数量的内核线程,例如某些轻量级的线程库,对用户来说如果有三个线程同时执行,对内核来说只有一个线程。

  一对一模型
  对于直接支持线程的系统,一对一模型始终是最为简单的模型。对一对一模型来说,一个用户态使用的线程就唯一对应一个内核的线程(但反过来不一定,一个内核里的线程在用户态不一定有对应的线程存在)
在这里插入图片描述

  一对一模型的优点是,线程和线程之间是真正的并发,一个线程因为某种原因阻塞时,其他线程执行完全不会受影响。此外,一对一模型也可以让多线程程序在多处理器的系统上有更好的性能表现。
  一对一线程缺点:
  由于许多操作系统限制了内核线程的数量,因此一对一线程会让用户的线程数量收到限制。
  许多操作系统内核线程调度时,上下文切换开销较大,导致用户线程的执行效率下降。

  多对一模型
  多对一模型将多个用户线程映射到一个内核线程上,线程之间的切换由用户态代码来执行,因此相对于一对一模型,多对一模型切换要快速许多。
在这里插入图片描述

  多对一模型大问题是,如果其中一个用户线程阻塞,那么所有线程都将无法运行,因为此时内核里的线程也随之阻塞了。另外在多处理器系统上,处理器增多对多对一模型的线程性能也不会有明显的帮助。但同时,多对一模型得到的好处是高效的上下文切换和几乎无限制的线程数量。

  多对多模型
  多对多模型结合了多对一模型和一对一模型的特点,将多个用户线程映射到少数但不止一个内核线程上。
  在多对多模型中,一个用户线程阻塞并不会使得所有的用户线程阻塞,因为此时还有别的内核线程可以被用来调度执行。另外,多对多模型对用户线程的数量也没有什么限制,在多处理器系统上,多对多模型的线程也能得到一定的性能提升。不过提升的幅度不如一对一线程模型高。
在这里插入图片描述

标签:获取,模型,用户,信号量,程序员,修养,线程,内核
来源: https://blog.csdn.net/qq_31866157/article/details/113794336