读书笔记-APUE第三版-(11)线程

Pthreads即POSIX标准线程。线程共享进程资源,包括程序text正文段,进程的全局变量、堆、栈和文件描述符等。在一个进程内,线程由代表可执行上下文的必要信息组成:线程ID,一系列寄存器值,栈,调度优先级,信号掩码等。

线程ID

线程ID只在同一进程内具有唯一性。线程ID用pthread_t数据类型表示,Linux实际为无符号长整型,而Mac OSX用的是指向pthread结构体的指针,所以比较线程ID得用pthread_equal方法。

#include <pthread.h> 
pthread_t pthread_self(void); //获取当前线程ID
int pthread_equal(pthread_t tid1, pthread_t tid2);//比较线程ID

线程创建

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, 
    const pthread_attr_t *restrict attr, 
        void *(*start_rtn)(void), void *restrict arg);
  1. tidp指向返回的线程ID。
  2. attr自定义线程属性下章介绍,先使用NULL。
  3. start_rtn新建线程的启动方法,arg为传递给start_rtn的参数。
  4. pthread库函数通常返回错误码,而不是像其他函数一样修改errno,保留error(每个线程一份拷贝)目的只是向后兼容。

线程终止

退出线程的方法有以下几种:

  1. 线程正常退出。
  2. 被同一进程中别的线程取消(cancel)。
  3. 调用pthread_exit函数。
  4. 线程调用exit,_exit函数会导致整个进程退出。
  5. 信号发送给线程,如果默认动作是终止进程,也会终止整个进程。

    #include <pthread.h> 
    void pthread_exit(void *rval_ptr);
    int thread_join(pthread_t thread, void **rval_ptr);
    int pthread_cancel(pthread_t tid);
    void pthread_cleanup_push(void (*rtn)(void *), void *arg);
    
  6. pthread_exit函数中rval_ptr参数是无类型指针,和传递给上文中创建线程start_rtn启动函数的arg参数类似。如果调用thread_join函数,线程的返回值会被用到,所以rval_ptr不能是局部变量,否则线程结束时内存会被回收导致错误。

  7. pthread_join函数用于阻塞当前线程,直到thread参数代表的线程(处于detached状态)终止,rval_ptr参数指向其返回值。
  8. pthread_cancel用于取消指定线程,它只是发出取消请求,立即返回。
  9. pthread_cleanup_push和进程的atexit函数类型,注册线程终止前的清理函数(pthread_exit和cancel时都清理函数都会被调用)

线程同步

Mutexes互斥锁

互斥锁的使用方法比较简单,在线程访问共享资源之前加锁,完成之后释放锁,互斥锁会保证同一时间只有一个线程能够加锁成功,其他线程会一直阻塞到锁被释放。

pthread_mutex_lock(pthread_mutex_t *mutex);
……//访问共享资源
pthread_mutex_unlock(pthread_mutex_t *mutex);

死锁

常见的一种死锁场景发生于各线程试图获取多个锁,而且锁定顺序不一致时。比如线程A先获取锁1,然后试图获取锁2;而同时线程B已经获取锁2,然后试图获取锁1,双方都被阻塞导致锁无法被释放。所以通过精心安排各线程获取锁的顺序可以避免锁。

int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
   const struct timespec *restrict abstime);

这两个函数都能有效避免死锁:

  1. trylock在mutex已经上锁的情况下不会阻塞,会立即返回EBUSY。
  2. timedlock超时会返回ETIMEDOUT

Reader-Writer Locks读写锁

也被称之为共享-排他锁。允许多个线程获取读锁,而只能有一个线程持有写锁;而且读锁只会阻塞写锁,写锁阻塞其他所有锁,所以更适用于读多写少的场景。

#include <pthread.h> 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//获取读锁,同时也有tryrdlock和timedlock版本
int pthread_rwlock_rwlock(pthread_rwlock_t *rwlock);//获取写锁,同时也有tryrwlock和timedlock版本
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//释放锁的接口是一致的

Condition Variables条件变量

条件变量和互斥锁一起配合使用,不满足条件时放弃锁等待合适的条件,能够减少资源竞争。条件变量本身被互斥锁“保护”,所以改变条件变量状态之前首先得获取互斥锁。

#include <pthread.h> 
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t  *restrict mutex);//有超时版本
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
  1. 线程获取互斥锁之后,如果某个条件不满足,就调用wait等待,这时会释放锁,阻塞。
  2. 另一个线程用signal通知wait的某个线程;broadcast会通知所有wait的线程。
  3. 阻塞的线程被唤醒,会重试尝试获取锁,wait返回(也可能获取锁失败,继续阻塞)

Spin Locks自旋锁

自旋锁和互斥锁类似,但是没有获取到锁的线程不会sleep,而是通过busy-wait-loop的方式来获取锁,所以要求使用自旋锁的临界区尽量简短,锁会被尽快释放掉,否则会导致cpu占用飙升。自旋锁通常作为较低层次的原语来实现其他类型锁(实际上,某些互斥锁的实现也会自旋一定时间来获取锁)

Barriers屏障

屏障允许一组线程互相等待,直到所有线程到达某个公共屏障点,然后继续往下运行。和join有点类似,但更强大:允许任意多个线程加入,也不用等到线程退出。常用于将一个大任务拆解为多个子任务由多个线程并发完成,然后主线程继续执行合并处理任务。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值