线程同步与互斥:
我们先来一个示例看一下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
int i = 10;
void* fun(void* arg)
{
char* id = (char *)arg;
while(1)
{
if(i > 0)
{
sleep(1);
printf("%s say i=%d\n",id,i);
i--;
}
else
{
break;
}
}
return NULL;
}
int main()
{
int i = 0;
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,fun,"1");
pthread_create(&tid2,NULL,fun,"2");
pthread_create(&tid3,NULL,fun,"3");
pthread_create(&tid4,NULL,fun,"4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
return 0;
}
按照我们理解的这个函数应该会输出:i从10—1的顺序。那么结果呢?
怎么会出现呢-1,-2呢?
这就是这个程序会出错的表面原因。那根本原因是什么呢?
这里就涉及到了原子性的概念,我们来看一下在cpu的角度下对待–操作是什么样的?
扩展一个指令,查看当前程序的汇编代码:
[test@localhost lianxi]$ objdump -d test > test.objdump
400724: 8b 05 22 09 20 00 mov 0x200922(%rip),%eax # 60104c <i>
40072a: 83 e8 01 sub $0x1,%eax
40072d: 89 05 19 09 20 00 mov %eax,0x200919(%rip) # 60104c <i>
这是函数执行的汇编代码中“ - - ”操作。我们可以看到–操作在cpu中执行了3步,第一步将i的值移动到寄存器中,第二步cpu进行-1操作,第三步将操作完成的值写回到i的地址处。
这个中间的过程中如果进行了线程切换,那将会产生错误。
归根结底还是–操作不是原子性操作。
想解决这个问题,很简单就是创建一个锁。每次对一个值进行操作的前提必须拥有锁。而我们知道线程中是有共享的内存的,所以我们就在共享内存的地方创建一个锁,使每个线程都可以看到这把锁。Linux中称这把锁为“互斥量”。
互斥量:
一、互斥锁
互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。
需要的头文件:pthread.h
互斥锁标识符:pthread_mutex_t
(1)互斥锁初始化:
函数原型: int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
函数传入值: mutex:互斥锁。
mutexattr:PTHREAD_MUTEX_INITIALIZER 创建快速互斥锁。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁。
函数返回值:成功:0;出错:-1
(2)互斥操作函数
int pthread_mutex_lock(pthread_mutex_t* mutex); //上锁
int pthread_mutex_trylock (pthread_mutex_t* mutex); //只有在互斥被锁住的情况下才阻塞
int pthread_mutex_unlock (pthread_mutex_t* mutex); //解锁
int pthread_mutex_destroy (pthread_mutex_t* mutex); //清除互斥锁
函数传入值:mutex:互斥锁。
函数返回值:成功:0;出错:-1
我们改进一下之前的代码:
int i = 10;
pthread_mutex_t mutex;
void* fun(void* arg)
{
char* id = (char *)arg;
while(1)
{
pthread_mutex_lock(&mutex);
if(i > 0)
{
printf("%s say i=%d\n",id,i);
i--;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
sleep(1);
}
return NULL;
}
int main()
{
int i = 0;
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,fun,"1");
pthread_create(&tid2,NULL,fun,"2");
pthread_create(&tid3,NULL,fun,"3");
pthread_create(&tid4,NULL,fun,"4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
return 0;
}
效果如下:
这样效果就出来了吧!
线程同步:(条件变量)
- 当线程访问互斥的某个变量时,它在其他线程没有释放“锁”之前什么也干不了。
- 例如动物园里饲养员给动物为食,在饲养员正在放食物的时候,动物被关在笼子了,什么也干不了。只有当饲养员把食物放好后,打开笼子,动物才能吃东西。
有关函数:
int pthread_cond_destroy(pthread_cond_t *cond);
//删除条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
//初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//创建
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
//等待条件满足(动物在笼子等待饲养员喂食物的过程)
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒多个线程(饲养员放好了食物,打开了许多动物的笼子)
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒单个线程(饲养员放好了食物,只打开了一个动物的笼子)
示例:
pthread_cond_t cond;
pthread_mutex_t mutex;
void* fun()
{
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("tager\n");
}
}
void* fun2()
{
while(1)
{
pthread_cond_signal(&cond);
sleep(1);
}
}
int main()
{
int i = 0;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun,NULL);
pthread_create(&tid2,NULL,fun2,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_cond_destroy(&cond);
return 0;
}
效果实例:
在这个pthread_cond_wait函数中为什么第二个参数要是一个互斥锁呢?
条件等待是线程通信间的一种必要的方式,如果一个线程所需要的资源不满足,他就会一直等待,所以必须要有另一个线程来改变它,这时就是俩个线程共享一块资源了,所以必须要加上锁。