条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。适合多个线程等待某个条件的发生,不使用条件变量,那么每个线程就不断尝试互斥锁并检测条件是否发生,浪费系统资源。通常条件变量和互斥锁同时使用。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件,可以用来实现线程间的同步。条件变量系统 API 如下:
读写锁
互斥量要么是加锁状态,要么是不加锁状态,而且一次只有一个线程对其进行加锁。读写锁可以有3种状态:读加锁状态、写加锁状态和不加锁状态。一次只有一个线程可以占有写模式读写锁,但是可以有多个线程同时占有读模式的读写锁。因此,读写锁适合于对数据结构的读次数比写次数多得多的情况,且读写锁比互斥量具有更高的并行性。读写锁加锁规则1:如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;2:如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。读写锁系统 API
自旋锁
互斥锁得不到锁时,线程会进入休眠,引发任务上下文切换,任务切换涉及一系列耗时的操作,因此用互斥锁一旦遇到阻塞切换代价是十分昂贵的。而自旋锁阻塞后不会引发上下文切换,当锁被其他线程占有时,获取锁的线程便会进入自旋,不断检测自旋锁的状态,直到得到锁,所谓的自旋就是循环等待的意思。自旋锁在用户态使用的比较少,在内核使用的比较多。自旋锁适用于临界区代码比较短,锁的持有时间比较短的场景,否则会让其他线程一直等待造成饥饿现象。自旋锁 API 接口
可以看到,多线程模型为了保证各个线程并行工作,需要额外做很多线程间的同步和通知工作,而且线程频繁的在阻塞和唤醒间切换,我们知道 Linux 下线程是轻量级线程 LWP ,每次线程切换涉及用户态和内核态的切换,还是很消耗性能的。同样的场景在协程模型里是怎么处理的呢?还是用前面的例子,说明协程模型的执行流程。
前面讲的多线程、多进程、协程都还只是软件层面的提高服务处理能力。真正硬核的是从硬件层面提高处理能力,增加 CPU 物理核心数目,当然硬件都是有成本的,所以只有软件层面已经充分榨干性能才会考虑增加硬件。不过,老板有钱买最好最贵的服务器另说,这是人民币玩家和穷逼玩家的区别了,软件工程师留下了贫困的泪水。
增加机器核心数
CPU领域有一条摩尔定律:大概 18 个月会将芯片的性能提高一倍。现在这个定律变的越来越难以突破,CPU 晶体管密度工作频率很难再提高,转而通过增加 CPU 核心数目的方式提高处理器性能。目前商用服务器架构基本都是多核处理器,多核的处理器能够真正做到程序并行运行,处理效率大幅度提升,那该如何查看 CPU 核心数目呢?对于 Windows 操作系统,打开任务管理器,通过界面的「内核」和「逻辑处理器」能看到。
查看 cpu 核心数
对于 Linux 操作系统,通过下面 2 种方式查看 CPU 核心相关信息。
1. 通过cpuinfo文件查看
使用cat /proc/cpuinfo查看 cpu 核心信息,如下两个信息:
processor,指明第几个cpu处理器
cpu cores,指明每个处理器的核心数
cpuinfo 输出示例:
2. 通过编程接口查看
除了上面以文件的形式查看 cpu 核心信息之外,系统还提供了编程接口可以查询,系统 API 如下。
CPU亲和性
CPU 亲和性是绑定某一进程或线程到特定的 CPU 或 CPU 集合,从而使得该进程或线程只能被调度运行在绑定的 CPU或 CPU 集合上。
为什么要设置 CPU 亲和性绑定 CPU 呢?理论上进程上一次运行后的上下文信息会保留在 CPU 的缓存中,如果下一次仍然将该进程调度到同一个 CPU 上,就能避免缓存未命中对 CPU 处理性能的影响,从而使得进程的运行更加高效。
假如某些进程或线程是 CPU 密集型的,不希望被频繁调度,又或者你有其他特殊需求,不希望进程或线程被调度在不同 CPU 之间频繁切换,则可以将该进程或线程绑定到特定的 CPU 上 ,可以在特定场景下优化程序性能。
绑定进程
在多进程模型中,绑定进程到特定的核心,下面是绑定进程的系统 API
绑定线程
在多线程模型中,绑定线程到特定的核心,下面是绑定线程的系统 API
总总结结
本文从程序任务类型出发,区分任务为 CPU 密集型和 IO 密集型两大类。接着分别说明提高基于这两类任务的服务性能方法,分为软件层面的方法和硬件层面的方法。其中软件层面主要讲述利用多进程、多线程以及协程模型,当然现有的技术还有 IO 多路复用、异步 IO 、池化技术等方案,讲到多线程和多进程,顺势说明了进程间通信和线程间同步互斥技术。第二部分,讲解了从硬件层面提高服务性能:提高机器核心数,并教你如何查看 CPU 核心数的方法。最后,还可以通过软硬结合的方式,把硬件核心绑定到指定进程或者线程执行,最大程度的利用 CPU 性能。希望通过本文的学习,读者对高性能服务模型有个初步的了解,并能对服务优化的方法和利弊举例一二,就是本文的价值所在。