写在前面
过去的一周,了解线程的基本概念,和各种控制原语,而更为重要的是线程同步的几种方式。为维护数据的顺序一致性,多线程之间需要同步完成对数据的操作,包括互斥量、读写锁、条件变量、自旋锁、屏障等方式。
第八章和第九章是进程的概念,对比线程和进程来学习。
一、线程的基础理解
学进程的时候,有进程间同步。而我们提起线程时,会往往想起“多线程”,线程比起进程有什么好处呢?
1、对于文件描述符共享或者内存共享,是依靠进程通信实现的,这需要使用一些较复杂的机制。而对于线程而言,可自由的访问相同的存储地址空间和文件描述符。
2、可以多线程执行任务分解提高程序的吞吐量。一个线程执行任务时,任务之间是串行的。当可分解为若干个不互相依赖的任务时,由不同线程执行,就可交叉进行,提高吞吐量。
3、交互的程序可以通过多线程来改善响应时间。对于那些与用户交互的程序,可以单独设定一个线程,处理程序与用户之间的交互。
多线程不论是在内存共享、还是吞吐量和响应时间上,都有明显的优势。所以,了解了多线程,就要在实际的程序中用到它!
这里有一个误区,
多线程!=多处理器!=多核,具体是什么原因呢,先刨一个坑,嘿嘿~
二、线程和进程的区别
本人根据了解的线程和进程的内容,整理的线程与进程的区别,欢迎大家讨论~
2.1、概念
进程是系统进行资源分配和调度的基本单位,在系统中是有意义的
线程比进程粒度更小,只在所属的进程上下文中有意义。典型的进程可看成只有一个控制线程,多线程是指程序在某一时刻不止做一件事。
2.2、标识
进程ID:非负整数,且唯一,pid_t类型,
线程ID:pthread_t类型,是一个结构,不是整数
2.3、控制原语
描述 | 线程 | 进程 |
---|---|---|
创建新的控制流 | pthread_create | fork |
从现有控制流退出 | pthread_exit | exit |
从控制流中得到退出状态 | pthread_join | waitpid |
注册在退出控制流时函数 | pthread_cancel_push | atexit |
获取控制流的ID | pthread_self | getpid |
请求控制流的非正常退出 | pthread_cancel | abort |
2.4、终止条件
进程终止:
显示或隐式(调用exit)调用_exit或Exit自愿终止,也可非自愿的由一个信号终止
线程终止:
1、简单的从调用例程返回,返回值是线程的退出码
2、可以被同一进程中的其他线程取消
3、调用pthread_exit
三、线程同步
3.1 为何需要同步?
当多个线程共享同一内存时,需要确保每个线程看到一致数据视图。如果线程只是读变量,或者变量是只读的,那么不会出现一致性问题。往往是,一个线程对变量A进行修改,在同一时间,另一个线程也需要修改变量A,而在计算机系统结构中,修改,可以理解为写操作,是一种增量操作,它需要3个步骤:
1、从内存单元读入寄存器
2、在寄存器中对变量做增量操作
3、从寄存器中协会内存单元
显而易见,增量操作并不是原子操作。所以,为保证数据的顺序一致性,线程之间需要同步机制
3.2 线程同步的五种方式
机制 | 机制说明 | 备注 |
---|---|---|
互斥量 | 本质上是锁,使用时加锁、解锁,加锁后试图再加锁的线程变成阻塞状态 | 初始化、反初始化 |
读写锁 | 比互斥量有更高的并行性,加读锁、加写锁、解锁。加读锁时,写锁被阻塞,第一个写锁之前的所有读锁一直进,之后的读锁被阻塞 | 初始化、反初始化 |
条件变量 | 条件变量和互斥量一起使用,提供多线程回合的场所。要想占用资源,先获得互斥量,再等待条件状态改变。其他线程可在改变条件状态之后再发信号 | 初始化、反初始化 |
自旋锁 | 类似于互斥量,在获取锁之前不是休眠而是处于忙等状态 | 初始化、反初始化 |
屏障 | 协调多个线程并行同步工作的机制,允许每个线程等待,直到所有合作线程到达某一点,然后从该店继续执行 | 需初始化 |
这五种方式根据自身特点,有各自应用的场景。互斥量是基本的锁,加锁解锁即可。而读写锁在互斥量的基础上提高了并行性,对于读操作,允许共享,而写操作互斥,因此读写锁也称为共享互斥锁。条件变量可以看做是一种对状态的保护,首先占有互斥量,并且等待状态改变才可以开始/结束对资源占用(由用户指定)。因此,提供给多个线程一个回合点。条件变量可看做多线程对某一个资源占用时,由条件变量决定使用时机。而屏障,更广的概念,允许任意数量的线程等待,直到所有的线程处理完工作,而线程不需要退出,所有线程到达屏障之后才能继续工作。自旋锁常用于系统底层原语实现其他的锁。