线程

标签: 互斥锁  信号量  条件变量  读写锁

线程

目录:

由于同一进程的多个线程之间共享同一地址空间,因此它的代码段和数据段都是共享的。除此之外,各线程还共享进程资源和环境

  • 文件描述符表
  • 每种信号的处理方式(SID_IGN、SIG_DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

但有些资源是每个线程各有一份

  • 线程id
  • 上下文,包括各种寄存器的值、程序计数器和栈指针
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

我们所要学习的线程库函数是由 POSIX 标准定义的,称为 POSIX thread 或者pthread。在 Linux 上线程函数位于 libpthread 共享库中,因此在编译时要加上-lpthread 选项。

1.线程控制
1.1创建线程
int pthread_create(pthread_t *thread, //线程标识符
                   const pthread_attr_t *attr,//线程属性NULL 
                   void *(*start_routine)(void*),//线程执行函数
                   void *arg);//线程处理函数的参数

返回值:若成功返回0;失败返回错误码

这里写图片描述

1.2终止线程
  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
  • 线程可以调用pthread_ exit终止自己
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程
#include <pthread.h>
void pthread_exit(void *value_ptr);  //只会退一个线程,不会退进程

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程的得到这个返回指针时线程函数已经退出了。

1.3线程等待和分离
//线程等待
int pthread_join(pthread_t thread,
                 void** arg);   //线程执行完带回来的数据
返回值:成功返回0,失败返回错误码

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它所占用的所有资源,而不保留终止状态。

//线程分离
int pthread_detach(pthread_t thread);//不会出现僵尸进程
返回值:成功返回0,失败返回错误码

对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一个线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

例:对一个线程既调用了pthread_detach,又调用了pthread_detach
这里写图片描述

2.线程间同步
2.1Mutex(互斥锁)

这里写图片描述
对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁。

1.定义互斥锁的全局变量pthead_mutex_t;    
2.初始化 pthread_mutex_init(&mutex, NULL);   //mutex = 1;
3.上锁 pthread_mutex_lock(&mutex);           //mutex == 1  ? 将mutex置0,返回
                                             //mutex == 0  ? 等待
4.解锁 pthread_mutex_unlock(&mutex);         //mutex = 1;  返回
5.销毁  pthread_mutex_destroy(&mutex);

一个线程可以调用pthread_mutex_lock获得Mutex,如果这时,另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
这里写图片描述

死锁

一般情况下,如果同一个线程先后两次调用调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此永远就处于挂起等待状态了,这就叫做死锁。
另外一种典型的死锁情形是:
这里写图片描述

2.2条件变量

线程间同步还有一种情况 :线程 A 需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程 A 就阻塞等待,而线程 B 在执行过程中使这个条件成立了,就唤醒线程 A 继续执行。在 pthread 库中通过条件变量来阻塞等待一个条件,或者唤醒等待这个条件的线程。条件变量用 pthread_cond_t 类型的变量表示。

1. 定义条件变量pthread_cond_t cond;
2. 初始化pthread_cond_init(&cond, NULL);
3. 等待条件pthread_cond_wait(&cond, &mutex); // 阻塞,将mutex置成1,
                                         // 返回,将mutex恢复成原样
4. 修改条件pthread_cond_signal(&cond);
5. 销毁条件变量pthread_cond_destroy(&cond);

一个 条件变量总是和一个Mutex搭配使用的。

例子:线程2给线程1发送一个信号,使条件满足
这里写图片描述
使用规范:
消费者:

pthread_mutex_lock(&mutex);
while ( 条件不满足 )  //  while 防止假唤醒
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

生产者:

pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond); // 如果没有线程在wait,signal就丢失
pthread_mutex_unlock(&mutex);

例子:生产者和消费者
这里写图片描述

2.3 POSIX信号量

信号量(Semaphore)和 Mutex 类似,表示可用资源的数量,和 Mutex 不同的是这个数量可以大于 1。

//初始化信号量
int sem_init(sem_t *sem,
             int pshared,  // 0 表示本进程内多个线程间的同步互斥
             unsigned int value) // 设置初值
// 销毁信号量
int sem_destroy(sem_t *sem);
// P操作(等待信号量)
int sem_wait(sem_t *sem);
//V操作 (发布信号量)
int sem_post(sem_t *sem);

例:生产者消费者
这里写图片描述
这里写图片描述

2.4读写锁

读写锁: 读读共享,读写互斥,写优先级高

1. pthread_rwlock_t rwlock;
2. pthread_rwlock_init(&rwlock, NULL); //初始化
3. pthread_rwlock_rdlock(&rwlock); //加锁
   pthread_rwlock_wrlock(&rwlock);
4. pthread_rwlock_unlock(&rwlock);//解锁
5. pthread_rwlock_destroy(&rwlock);//销毁

例子: 读写锁,2个写者线程,3个读者线程
这里写图片描述

原文链接:加载失败,请重新获取