临界区定义:函数内同时运行多个线程时引起的,由多条语句构成的代码块。
当多个线程对同一块内存空间进行操作时,就有可能产生临界区问题。
下面先看一个例子:
#include
#include
#include
#include
#define NUM_THREAD 100
void *thread_inc(void *arg);
void *thread_des(void *arg);
long long num = 0; //64 bit
int main(int argc, const char * argv[]) {
pthread_t thread_id[NUM_THREAD];
int i;
printf("sizeof long long: %ld \n",sizeof(long long));
for (i = 0; i < NUM_THREAD; i++) {
if (i%2) {
pthread_create(&thread_id[i], NULL, thread_inc, NULL);
}else{
pthread_create(&thread_id[i], NULL, thread_des, NULL);
}
}
for (i = 0; i < NUM_THREAD; i++) {
pthread_join(thread_id[i], NULL);
}
printf("result: %lld \n",num);
return 0;
}
void *thread_inc(void *arg){
int i;
for (i = 0; i < 50000000; i++) {
num += 1; //临界区
}
return NULL;
}
void *thread_des(void *arg){
int i;
for (i = 0; i < 50000000; i++) {
num -= 1; //临界区
}
return NULL;
}
以上代码运行,理论上运行的结果为0,可是因为存在临界区问题,每次运行的结果都不一样。
引起问题的3种情况:
(1)2个线程同时执行thread_inc函数。
(2)2个线程同时执行thread_des函数。
(3)2个线程分别执行thread_inc函数和thread_des函数。
关于(3)的结论:如果2条语句访问同一内存空间,那么这2条不同语句由不同线程同时执行时,也有可能构成临界区。
对于多个线程对临界区进行访问,可以用线程同步来解决。
实现线程同步的两种方式:互斥量和信号量。
互斥量(加锁):
实现的语句:
pthread_mutex_t mutex; //定义互斥量
pthread_mutex_init(&mutex, NULL); //初始化互斥量,NULL表示不需要设置互斥量(锁)的属性
pthread_mutex_destroy(&mutex); //销毁互斥量
pthread_mutex_lock(&mutex); //进入临界区时加锁
pthread_mutex_unlock(&mutex); //离开临界区时释放锁
进入临界区前调用 pthread_mutex_lock 函数,当调用该函数时,发现有其他线程已经进入临界区,则 pthread_mutex_lock 不会返回,直到里面的线程调用 pthread_mutex_unlock 函数退出临界区为止。也就是说其他线程让出临界区之前,当前线程将一直处于阻塞状态。
代码编写方法:
pthread_mutex_lock(&mutex); //进入临界区时加锁
//临界区开始
......
//临界区结束
pthread_mutex_unlock(&mutex); //离开临界区时释放锁
使用互斥量解决多线程访问临界区问题:
#include
#include
#include
#include
#define NUM_THREAD 100
void *thread_inc(void *arg);
void *thread_des(void *arg);
long long num = 0; //64 bit
pthread_mutex_t mutex;
int main(int argc, const char * argv[]) {
pthread_t thread_id[NUM_THREAD];
int i;
pthread_mutex_init(&mutex, NULL); //创建互斥量
printf("sizeof long long: %ld \n",sizeof(long long));
for (i = 0; i < NUM_THREAD; i++) {
if (i%2) {
pthread_create(&thread_id[i], NULL, thread_inc, NULL);
}else{
pthread_create(&thread_id[i], NULL, thread_des, NULL);
}
}
for (i = 0; i < NUM_THREAD; i++) {
pthread_join(thread_id[i], NULL);
}
printf("result: %lld \n",num);
pthread_mutex_destroy(&mutex); //销毁互斥量
return 0;
}
void *thread_inc(void *arg){
int i;
pthread_mutex_lock(&mutex); //进入临界区时加锁
for (i = 0; i < 50000000; i++) {
num += 1; //临界区
}
pthread_mutex_unlock(&mutex);
return NULL;
}
void *thread_des(void *arg){
int i;
pthread_mutex_lock(&mutex);
for (i = 0; i < 50000000; i++) {
num -= 1; //临界区
}
pthread_mutex_unlock(&mutex);
return NULL;
}
使用互斥量后执行的结果正常,为0。
信号量:
注意:目前该方法已经被废弃了,这里也顺便罗列出来,感兴趣的可以研究学习一下。
实现的语句:
sem_t sem_one; //定义信号量
sem_init(&sem_one, 0, 0);//第二个参数0表示只允许1个进程内部使用的信号量
//第三个参数0表示设置信号量的初始值为0
sem_destroy(&sem_one); //销毁信号量
sem_wait(&sem_one); //使信号量减1
sem_post(&sem_one); //使信号量增1
原理:
调用 sem_init 函数时,操作系统会初始化信号量对象并记录下信号量的值。该值在调用 sem_post 函数时增1,调用 sem_wait 函数时减1。但是该信号量的值不能小于0,当信号量为0时,调用sem_wait 函数的线程会进入阻塞状态,直到有其他线程对该信号量调用 sem_post 函数,将信号量的值变为1,这样原本阻塞的线程就可以将该信号量重新减为0并跳出阻塞状态。
代码编写方法:
sem_wait(&sem_one); //调用前,信号量应变为0
//临界区的开始
......
//临界区的结束
sem_post(&sem_one); //使信号量增1
实例:
#include
#include
#include
void *readstr(void *arg);
void *accu(void *arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;
int main(int argc, const char * argv[]) {
pthread_t id_t1,id_t2;
sem_init(&sem_one, 0, 0);//第二个参数0表示只允许1个进程内部使用的信号量
//第三个参数0表示设置信号量的初始值为0
sem_init(&sem_two, 0, 1);
pthread_create(&id_t1, NULL, readstr, NULL);
pthread_create(&id_t2, NULL, accu, NULL);
pthread_join(id_t1, NULL);
pthread_join(id_t2, NULL);
sem_destroy(&sem_one);
sem_destroy(&sem_two);
return 0;
}
void *readstr(void *arg){
int i;
for (i = 0; i < 5; i++) {
fputs("Input num:", stdout);
sem_wait(&sem_two);
scanf("%d",&num);
sem_post(&sem_one);
}
return NULL;
}
void *accu(void *arg){
int sum = 0,i;
for (i = 0; i < 5; i++) {
sem_wait(&sem_one);
sum += num;
sem_post(&sem_two);
}
printf("Result: %d\n",sum);
return NULL;
}
以上代码实现累加输入的5个数,线程1输入,线程2做累加。线程1每输入一个数,线程2就累加一次,在线程1没有输入时,线程2阻塞等待。