1. 线程的概念
【操作系统】2.进程和线程 - imXuan - 博客园 (cnblogs.com)
- 线程:light weight process(LWP)轻量级的进程,在 Linux 中本质上仍然是一个进程
- 进程:有独立的地址空间,独立PCB,可以当作只有一个线程的进程。进程是计算机资源分配的最小单位
- 线程:有独立的PCB,共享物理地址空间,是最小的执行单位。cpu时间片划分以PCB为依据,是调度的基本单位
- LWP号:cpu划分时间片的依据。指令 “ ps -Lf pid “ 查看
1.1 线程共享的资源
- 文件描述符表
- 每种信号的处理方式(多个线程会争抢一个信号)
- 当前工作目录
- 用户ID和组ID
- 内存地址空间 (.text/.data/.bss/heap/共享库) -> 堆区全局共享变量是共享的(进程是读时共享写时复制,实际上就是非共享)
1.2 线程非共享资源
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量(不是设置全局errno,直接返回errno)
- 信号屏蔽字(虽然共享信号,多个信号会争抢一个信号,但是可以使用信号屏蔽字)
- 调度优先级
1.3 线程优缺点
- 优点:
- 提高程序并发性
- 开销小
- 数据通信、共享数据方便
- 缺点:
- 库函数,不稳定
- 调试、编写困难、gdb不支持
- 对信号支持不好
- 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
2. 线程常用操作
- 创建线程:pthread_create
- 线程获取:pthread_self
- 线程退出:
- 线程内部:return void* (0);
- 线程内部:pthread_exit(void *(0));
- 线程外部:pthread_canel(返回值是 -1,需要一个取消点)
- 线程回收
- 手动回收:pthread_join
- 自动回收:pthread_detach
2.1 创建线程 pthread_create
功能:创建一个线程。
1 |
|
2.2.1 线程中处理出错
1 |
|
2.2 获取线程ID pthread_self
功能:获取线程号(与ps -Lf 查看的 id 不同)
1 |
|
2.3 线程退出 pthread_exit
功能:退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
- return:返回到调用者
- exit:退出进程
- pthread_exit:退出线程
1 |
|
2.4 线程回收 pthread_join
功能:等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
1 |
|
2.5 线程分离 pthread_detach
功能:使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的PCB资源。所以,此函数不会阻塞。
1 |
|
2.6 线程取消 pthread_cancel
功能:杀死(取消)线程
- 被 pthread_cancel() 杀死的线程,使用 pthread_join() 再进行回收,会得到返回值 -1
- 使用 pthread_cancel() 杀死线程必须有一个保存点才能生效,否则无法杀死线程。应该在被 cancel 函数调用的线程函数里自己添加一个取消点 pthread_testcancel(); 实际上就是进入系统内核,给他一个杀死线程的机会
1 |
|
3. 线程同步
3.1 互斥锁 pthread_mutex_t
互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )
1 |
|
3.2 读写锁 pthread_rwlock_t
当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。
读写锁的特点:
- 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作
- 如果有其它线程写数据,则其它线程都不允许读、写操作
读写锁分为读锁和写锁,规则如下:
- 如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁
- 如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁
举例子:线程1 给读写锁加了读锁,此时 线程2 请求读锁、线程3 请求写锁;则 线程2 的读锁会被阻塞,等 线程1 读锁释放后,线程3 进行写,之后 线程2 再读
1 |
|
3.3 条件变量 pthread_cond_t
与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁,条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。生产者消费者模型中比较常用
条件变量的两个动作:
- 条件不满, 阻塞线程
- 当条件满足, 通知阻塞的线程开始工作
条件变量的类型: pthread_cond_t
1 |
|
timespec结构体(abs_time 表示绝对时间,从1970年1月1日 00:00:00计算)
1 | struct timespec { |
一个示例代码
1 |
|
3.4 信号量 semaphore
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞
1 |
|
一个示例代码
1 |
|