当多个线程共享相同的内存的时候,需要确保每个线程都看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的。那么就不存在一致性问题。同样,如果变量是只读的,多个线程也不会有一致性的问题。但是当一个线程可以修改的变量其他线程也可以读取或者修改的时候。我们就需要对这些线程进行同步,确保它们在访问变量存储内容时不会访问到无效的值。
首先来看一个线程不同步的例子:
typedef struct foo_test{
int count;
int i;
}foo_test;
void mutex_try(foo_test *f){
pthread_t tid;
while (f->i<20){
tid=pthread_self();
f->count+=1;
printf("the thread id is %d,i is %d,count is %d ",tid,f->i,f->count);
f->i++;
}
pthread_exit((void *)2);
}
int main()
{
foo_test f;
pthread_t tid1,tid2;
void *tret;
f.count=0;
f.i=0;
pthread_create(&tid1,NULL,mutex_try,&f);
pthread_create(&tid2,NULL,mutex_try,&f);
pthread_join(tid1,&tret);
printf("thread 1 exit code %ld ",(long)tret);
pthread_join(tid2,&tret);
printf("thread 2 exit code %ld ",(long)tret);
return 0;
}
运行结果:可以看到当thread1的计数为1的时候,count值从1变成了3,正常应该是2.这是因为thread2在 i=0的时候也对count值增加了1, 因此当thread1在i=1访问的时候,count值就变成了3. 在这里两个线程访问了两次i=0. 这就是不同步导致的
为了解决这个问题,在线程中就必须用到锁,同一时间只允许一个线程访问该变量。也就是修改操作是原子操作。这样就不会存在竞争。可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥从本质上就是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放互斥量(解锁)
互斥变量是用pthread_mutex_t数据类型表示的,在使用互斥变量以前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化,如果动态分配互斥量例如通过malloc调用。在释放内存前需要调用pthread_mutex_destroy。
代码修改如下:
typedef struct foo_test{
int count;
pthread_mutex_t f_lock;
int i;
}foo_test;
void mutex_try(foo_test *f){
pthread_t tid;
while (f->i<20){
tid=pthread_self();
pthread_mutex_lock(&f->f_lock);
f->count+=1;
f->i++;
pthread_mutex_unlock(&f->f_lock);
printf("the thread id is %d,i is %d,count is %d ",tid,f->i,f->count);
// Sleep(1000);
}
pthread_exit((void *)2);
}
int main()
{
foo_test *f;
pthread_t tid1,tid2;
void *tret;
f=(foo_test *)malloc(sizeof(foo_test));
f->count=0;
f->i=0;
pthread_mutex_init(&f->f_lock,NULL);
pthread_create(&tid1,NULL,mutex_try,f);
pthread_create(&tid2,NULL,mutex_try,f);
pthread_join(tid1,&tret);
printf("thread 1 exit code %ld ",(long)tret);
pthread_join(tid2,&tret);
printf("thread 2 exit code %ld ",(long)tret);
return 0;
}
运行结果如下:可以看到两个线程对于变量i的使用没有重复的了。i的增加可以保持原子性。同理count也是一样