背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?
通过多线程模拟多窗口售票为例:
#include
#include
#include
#include
#include
#include
using namespace std;
int ticket_sum=20;
void *sell_ticket(void *arg)
{
for(int i=0; i0)
{
sleep(1);
cout<
ticket_sum--;
}
}
return 0;
}
int main()
{
int flag;
pthread_t tids[4];
for(int i=0; i<4; i++)
{
flag=pthread_create(&tids[i],null,&sell_ticket,null);
if(flag)
{
cout<return flag;
}
}
sleep(20);
void *ans;
for(int i=0; i<4; i++)
{
flag=pthread_join(tids[i],&ans);
if(flag)
{
cout<分析:总票数只有20张,却卖出了23张,是非常明显的超买超卖问题,而造成这个问题的根本原因就是同时发生的各个线程都可以对ticket_sum进行读取和写入!
ps:
1.在并发情况下,指令执行的先后顺序由内核决定,同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清楚是哪一个先执行,如果运行的结果依赖于不同线程执行的先后的话,那么就会形成竞争条件,在这样的情况下,计算的结果很难预知,所以应该尽量避免竞争条件的形成
2.最常见的解决竞争条件的方法是将原先分离的两个指令构成一个不可分割的原子操作,而其他任务不能插入到原子操作中!
3.对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源!
4.线程同步的常见方法:互斥锁,条件变量,读写锁,信号量
一.互斥锁
本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,一旦获得,这个互斥锁会锁上变成lock状态,此后只有该线程由权力打开该锁,其他线程想要获得互斥锁,必须得到互斥锁再次被打开之后
采用互斥锁来同步资源:
#include
#include
#include
#include
#include
#include
using namespace std;
int ticket_sum=20;
pthread_mutex_t mutex_x=pthread_mutex_initializer;//static init mutex
void *sell_ticket(void *arg)
{
for(int i=0; i0)
{
sleep(1);
cout<ticket_sum--;
}
pthread_mutex_unlock(&mutex_x);
}
return 0;
}
int main()
{
int flag;
pthread_t tids[4];
for(int i=0; i<4; i++)
{
flag=pthread_create(&tids[i],null,&sell_ticket,null);
if(flag)
{
cout<return flag;
}
}
sleep(20);
void *ans;
for(int i=0; i<4; i++)
{
flag=pthread_join(tids[i],&ans);
if(flag)
{
cout<ticket_sum--;
}
sleep(1);
pthread_mutex_unlock(&mutex_x);
sleep(1);
}
return 0;
}
void *sell_ticket_2(void *arg)
{
int flag;
for(int i=0; i<10; i++)
{
flag=pthread_mutex_trylock(&mutex_x);
if(flag==ebusy)
{
cout<{
sleep(1);
cout<ticket_sum--;
}
pthread_mutex_unlock(&mutex_x);
}
sleep(1);
}
return 0;
}
int main()
{
int flag;
pthread_t tids[2];
flag=pthread_create(&tids[0],null,&sell_ticket_1,null);
if(flag)
{
cout<return flag;
}
flag=pthread_create(&tids[1],null,&sell_ticket_2,null);
if(flag)
{
cout<return flag;
}
void *ans;
sleep(30);
flag=pthread_join(tids[0],&ans);
if(flag)
{
cout<分析:通过测试加锁函数我们可以清晰的看到两个线程争用资源的情况
二.条件变量
互斥量不是万能的,比如某个线程正在等待共享数据内某个条件出现,可可能需要重复对数据对象加锁和解锁(轮询),但是这样轮询非常耗费时间和资源,而且效率非常低,所以互斥锁不太适合这种情况
我们需要这样一种方法:当线程在等待满足某些条件时使线程进入睡眠状态,一旦条件满足,就换线因等待满足特定条件而睡眠的线程
如果我们能够实现这样一种方法,程序的效率无疑会大大提高,而这种方法正是条件变量!
样例:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
pthread_cond_t qready=pthread_cond_initializer; //cond
pthread_mutex_t qlock=pthread_mutex_initializer; //mutex
int x=10,y=20;
void *f1(void *arg)
{
cout<pthread_mutex_lock(&qlock);
while(x
{
pthread_cond_wait(&qready,&qlock);
}
pthread_mutex_unlock(&qlock);
sleep(3);
cout<return 0;
}
void *f2(void *arg)
{
cout<pthread_mutex_lock(&qlock);
x=20;
y=10;
cout<return 0;
}
int main()
{
pthread_t tids[2];
int flag;
flag=pthread_create(&tids[0],null,f1,null);
if(flag)
{
cout<return flag;
}
sleep(2);
flag=pthread_create(&tids[1],null,f2,null);
if(flag)
{
cout<return flag;
}
sleep(5);
return 0;
}
分析:线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然后线程2结束,然后线程1继续运行,然后线程1结束,为了确保线程1先执行,在创建线程2之前我们sleep了2秒
ps:
1.条件变量通过运行线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,常常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开响应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知响应的条件变量换线一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并且重新测试条件是否满足
1.条件变量的相关函数
1)创建
静态方式:pthread_cond_t cond pthread_cond_initializer
动态方式:int pthread_cond_init(&cond,null)
linux thread 实现的条件变量不支持属性,所以null(cond_attr参数)
2)注销
int pthread_cond_destory(&cond)
只有没有线程在该条件变量上,该条件变量才能注销,否则返回ebusy
因为linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程!(请参考条件变量的底层实现)
3)等待
条件等待:int pthread_cond_wait(&cond,&mutex)
计时等待:int pthread_cond_timewait(&cond,&mutex,time)
1.其中计时等待如果在给定时刻前条件没有被满足,则返回etimeout,结束等待
2.无论那种等待方式,都必须有一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait形成竞争条件!
3.在调用pthread_cond_wait前必须由本线程加锁
4)激发
激发一个等待线程:pthread_cond_signal(&cond)
激发所有等待线程:pthread_cond_broadcast(&cond)
重要的是,pthread_cond_signal不会存在惊群效应,也就是是它最多给一个等待线程发信号,不会给所有线程发信号唤醒提他们,然后要求他们自己去争抢资源!
pthread_cond_signal会根据等待线程的优先级和等待时间来确定激发哪一个等待线程
下面看一个程序,找到程序存在的问题
#include
#include
#include
#include
#include
#include
#include
using namespace std;
pthread_cond_t taxi_cond=pthread_cond_initializer; //taix arrive cond
pthread_mutex_t taxi_mutex=pthread_mutex_initializer;// sync mutex
void *traveler_arrive(void *name)
{
cout<pthread_mutex_lock(&taxi_mutex);
pthread_cond_wait(&taxi_cond,&taxi_mutex);
pthread_mutex_unlock(&taxi_mutex);
cout<pthread_exit((void*)0);
}
void *taxi_arrive(void *name)
{
cout<pthread_cond_signal(&taxi_cond);
pthread_exit((void*)0);
}
int main()
{
pthread_t tids[3];
int flag;
flag=pthread_create(&tids[0],null,taxi_arrive,(void*)(jack));
if(flag)
{
cout<return flag;
}
cout<