- 大部分情况下,线程使用的都是局部变量,保存在自己的私有栈结构里,但是有的时候又会多线程访问共享资源,如全局变量,这时候如果操作过于频繁,或者线程数量较多,就会产生一些问题,因为毕竟不是所有的操作都是原子性的(要么不做,要么就做完,不存在中间时刻)
- 下面模拟买票场景,大家看完就会明白问题所在,代码很简单
int ticket = 20;//假设一共有20张票(全局变量)
void* thread_run(void* arg)
{
const void* msg = (const void*)arg;
while(1)
{
if(ticket>0)
{
usleep(1000);//让每个线程等待一下,最后现象会更明显
printf("%s get a ticket,ticket: %d\n",msg,ticket);//打印当前剩余票数和购票人
ticket--;//票数减1
}else
{
break;
}
}
}
int main()
{
pthread_t tid1,tid2,tid3,tid4;
//创建四个线程,作为四个购票者
pthread_create(&tid1,NULL,thread_run,"thread1");
pthread_create(&tid2,NULL,thread_run,"thread2");
pthread_create(&tid3,NULL,thread_run,"thread3");
pthread_create(&tid4,NULL,thread_run,"thread4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
}
远行结果:
- 我们发现结果中出现了负数的情况,这显然是不合理的,为什么会产生这种情况呢?
- 答案很简单,四个线程在同时访问一个全局变量ticket,都进行了 减减操作,而减减的操作并不是原子操作,它会先把ticket变量从内存加载到寄存器中,更新寄存器里面的值,执行减减操作,再从寄存器写回其原内存地址中。所以在此期间,很有可能多个进程同时访问该变量,导致结果异常,那有什么解决办法呢?
- 解决问题的方法也很简单,就是规定一个线程在申请到该资源后,其他线程不能再继续申请,只能等待第一个线程释放掉该资源后,才能进行申请
- 举个例子,你想去超市买苹果,超市里只有几个苹果了,大家都争先恐后的去抢,而你又很幸运的第一个冲了进去,这时你怎么保证苹果拿到手后不被人抢走?很简单,当然是拿把锁把门锁上了,开开心心的吃完苹果后,再把锁开开不就完了么。线程中也是如此,为了保证线程之间的工作互不影响,我们引入了互斥与同步的概念,以及互斥量和条件变量
互斥
概念:事件A与事件B在任何一次试验中都不会同时发生,则称事件A与事件B互斥。
线程互斥:两个或多个线程不会同时访问同一块临界资源(共享资源),即多个线程互斥地访问同一块资源
为了实现多线程互斥行为,Linux下提供的这把锁被命名为互斥量。
互斥量的接口
初始化互斥量
- 方法1:静态分配
pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER
- 方法2:动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁和解锁
int pthread_mutex_lock(pthreade_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
调用pthread_lock时,可能会遇到以下情况
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁
售票系统代码改进后:
int ticket = 20;
pthread_mutex_t lock;//创建互斥锁,因为互斥锁要让每个进程都能看到,所以要创建成全局的
void* thread_run(void* arg)
{
const void* msg = (const void*)arg;
while(1)
{
pthread_mutex_lock(&lock);//在访问ticket变量前上锁
if(ticket>0)
{
usleep(1000);
printf("%s get a ticket,ticket: %d\n",msg,ticket);
ticket--;
pthread_mutex_unlock(&lock);//访问完后将互斥锁释放
}else
{
pthread_mutex_unlock(&lock);//释放互斥锁
break;
}
}
}
int main()
{
pthread_t tid1,tid2,tid3,tid4;
pthread_mutex_init(&lock,NULL);//初始化互斥锁
pthread_create(&tid1,NULL,thread_run,"thread1");
pthread_create(&tid2,NULL,thread_run,"thread2");
pthread_create(&tid3,NULL,thread_run,"thread3");
pthread_create(&tid4,NULL,thread_run,"thread4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
pthread_mutex_destroy(&lock);//销毁互斥锁,一定要在等待线程之后再销毁,因为其余四个线程在执行购票操作
return 0; //时,主线程在阻塞式的等待,如果主线程先把锁销毁了,就会导致其余线程阻塞
//因为都在等待申请锁,或者一个线程在未释放锁时,锁已经被销毁了
}
运行多次结果依旧正确,实现了多线程间的互斥行为,但是即使加上了互斥锁,也还是有小问题,大家发现了没,全部20张票都被线程1买走了,这能忍?各位都是要买票回家的,怎么着也应该都有票才是,为了解决这个问题,我们就引入了同步的概念
同步
概念:指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象。
线程间的同步:两个或两个以上的线程协同的访问共享资源,即线程A访问完之后,他不急着再去申请该资源,而是让给线程B去访问。
为了实现多线程同步行为,Linux引入了条件变量,用法和互斥量很相似
- 举例:假设有一个生产者,一个消费者,消费者只干一件事,就是买香蕉,生产者也只生产香蕉,当消费者去买的时候,发现香蕉没了,他不会每时每刻都去检查香蕉送来了没,而是会等待生产者的消息,生产者送来香蕉后,就会给消费者打电话,让他去取香蕉,这样两人互不干扰对方的事情,并且达到了一种协同。
- 条件变量类似于那个电话,当线程A发现它的条件不成立时(香蕉没了),就会在该条件下等待,并且释放掉锁(你总不能天天守着超市还不给人家送货的开门吧),而线程B得到锁后,就会去执行它的工作,并且改变条件变量,唤醒等待中的线程A(相当于打电话给他),并且释放掉锁,这时线程A被唤醒,并会重新得到锁,去执行接下来的代码,实现同步
条件变量函数
初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond);
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个变量上等待,并且在等待期间要释放锁;如果被正常或异常唤醒后,会重新获得锁并从等待处开始执行
mutex:互斥量
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒一群线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒单个线程
代码实现:
volatile int banana = 0;
pthread_cond_t cond1;//创建条件变量
pthread_mutex_t lock;//创建锁
void *comsumer(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);//先申请互斥锁
while(banana<=0)
{
printf("I want to eat a banana\n");
pthread_cond_wait(&cond1,&lock);//此时没有香蕉,就把锁释放,并在cond1号条件变量下等待
} else{
banana--;
printf("I eat a banana!\n");
}
pthread_mutex_unlock(&lock);//释放锁
}
}
void *product(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);//生产者申请锁
banana++;
printf("I put a banana\n");
int r = pthread_cond_broadcast(&cond1);//如果唤醒失败,则打印出错信息
if ( r != 0 ) {
fprintf(stderr, "=======%s\n", strerror(r));
}
pthread_mutex_unlock(&lock);
sleep(5);
}
}
int main()
{
pthread_t com,pro;
pthread_cond_init(&cond1,NULL);
pthread_mutex_init(&lock,NULL);
pthread_create(&com,NULL,comsumer,NULL);
pthread_create(&pro,NULL,product,NULL);
pthread_join(com,NULL);
pthread_join(pro,NULL);
pthread_cond_destroy(&cond1);//销毁条件变量也要在回收线程之后,原因同互斥量
pthread_mutex_destroy(&lock);
return 0;
}
运行结果显示两者协同工作,互不干扰