临界区
2019年5月19日
18:46
多个线程在同时调用函数时可能会产生问题,可能会产生问题的这部分代码称之为临界区(Critical Section)。
根据临界区是否会产生问题,函数可分为:
- 线程安全函数(Threa-safe function)
- 非线程安全函数(Thread-unsafe function)
线程安全函数被多个线程同时调用也没有问题,但是非线程安全函数就可能会引发问题。
大多数标准函数都是线程安全函数,我们不需要自己区分线程安全函数与非线程安全函数。因为他们大都数时候都提供了非线程安全函数的对应线程安全版本。
在涉及线程的代码中,我们可以同在在头文件声明前定义_REENTRANT宏来说明调用线程安全函数
也可以通过在编译时添加-D_REENTRANT选项定义宏
$ gcc -D_REENTRANT mythread.c -o mythread -lpthread
线程存在的问题和临界区
任何内存空间只要是被线程同时访问,就有可能发生问题。
为了解决这个临界区的问题其实很简单,就是不让不同线程同时访问一个变量。而实现这个就是 线程同步。
线程同步可以解决两方面的情况:
- 不能同时访问同一内存空间
- 需要指定访问同一内存空间的线程执行顺序
互斥量 Mutual Exclusion
表示不允许多个线程同时访问,互斥量主要用于结局线程同步的问题。
我们通过互斥量实现互斥锁,在一个线程在访问变量时就将他锁住,而等到访问完毕再释放这把锁。
创建与删除互斥量
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
->>成功时返回0,失败时返回其他值
mutex: 互斥量指针
attr:创建互斥量需要指定的属性,默认值则传递NULL
上锁与解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
当其他线程调用pthread_mutex_lock函数预备进入临界区时,如果发现有其他线程已经进入临界区。将使这个函数阻塞,一直到那个线程调用pthread_mutex_unlock解锁
pthread_mutex_lock(&mutex);
// 临界区开始
// ….
//临界区结束
pthread_mutex_unlock(&mutex);
如果线程退出临界区使,而没有调用pthread_mutex_unlock函数,那么其他线程的pthread_mutex_lock函数将一直处于阻塞状态。这种情况称之为死锁。
互斥量lock,unlock函数的频繁调用使程序的执行效率降低。所以应该对于不同的程序适当的考虑是应该扩大还是缩小临界区。
信号量 Semaphore
顾名思义。与互斥量的开锁与解锁相比。信号量就是给一个信号,看是否复合条件能通过。
信号量的创建和销毁
#include <semaphore.h>
int sum_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
->>成功时返回0,失败时返回其他值
sem: 保存信号量的地址
pshared: 传递其他值是,创建可有多个进程共享的信号量。传递0时,只允许一个进程内部使用。
value:信号量的初始值
相当于互斥量的开锁与解锁的函数
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
->>成功时返回0,失败时返回其他值
sem:保存信号量的地址
传递给sem_post函数时增加1
传递给sem_wait函数时减少1,当信号量值必须大于等于0
也就是通过调用sem_wait函数时检查信号量的值是否满足要求才继续下去,否则就阻塞。等到其他线程调用sem_post使这个信号量满足要求。
调用sem_wait函数进入临界区的线程再调用sem_post函数前不允许其他线程进入临界区。信号量的值在0和1之间跳转。具有这种特性的机制被称为"二进制信号量"。