当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,若每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性概念,同样地,若变量是只读的,多个线程同时读取该变量也不会有一致性问题,但是当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数值。
正如上一篇博客中写到的那样,对于这个变量的自增,多个线程同时访问一块内存空间时,某个线程还未完成它的操作,另外一个线程也进行了这个操作,导致结果与我们的与其有出入。
今天的博客我们就来解决这个问题:
线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行,线程同步的方法有四种:
概念
互斥锁只有两种状态,加锁状态和解锁状态,
如果一个线程对已经处于加锁状态的互斥锁进行加锁操作,则加锁操作会阻塞,直到正在对互斥锁加锁状态线程进行解锁操作。
互斥锁的接口
#include
pthread_mutex_t //互斥锁的类型
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
第二个参数为锁属性,一般传入NULL
加锁操作
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);
解决之前的问题:
多次测试后发现,wg的值一直都是5000,说明互斥锁发挥了作用。但是其实反复的加锁解锁操作在一定程度上也影响了程序的执行效率,但这也是必须的牺牲,为了程序的正常执行。
使用
示例:模拟两个线程竞争一个打印机,A线程使用打印机输出一个a,使用完成后输出一个a,B线程也一样,这样我们的输出结果不会出现abab/baba
如何让两个线程操作的是同一把互斥锁:将锁定义到全局
#include
#include
#include
#include
#include
#include
#include
pthread_mutex_t mutex;
void *threadFun(void *arg) //B线程
{
int i = 0;
for(;i < 5;i++)
{
pthread_mutex_lock(&mutex);
printf("B");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("B");
fflush(stdout);
pthread_mutex_unlock(&mutex);
n = rand() % 3;
sleep(n);
}
}
void threadMain() //A线程
{
int i = 0;
for(;i < 5;i++)
{
pthread_mutex_lock(&mutex);
printf("A");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("A");
fflush(stdout);
pthread_mutex_unlock(&mutex);
n = rand() % 3;
sleep(n);
}
}
int main()
{
srand((unsigned int)time(NULL));
pthread_mutex_init(&mutex,NULL);
pthread_t id;
int res = pthread_create(&id,NULL,threadFun,NULL);
assert(res == 0);
threadMain();
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
exit(0);
}
执行结果
概念
线程级信号量和进程级信号量的原理是相同的,信号量也是特殊的计数器,当值>0时,记录的是临界资源的个数,=0时,表示没有临界资源可用,这时对信号量执行P操作,则线程会被阻塞。
信号量的接口
#include
sem_t //线程级信号量的类型
初始化信号量并且给定初始值
int sem_init(sem_t *sem,int shared,int val);
对信号量执行P操作
int sem_wait(sem_t *sem);
对信号量执行V操作
int sem_post(sem_t *sem);
销毁信号量
int sem_destroy(sem_t *sem);
信号量和互斥锁的区别
概念
读写锁在互斥锁的基础上,允许一个更高的并行性,读写锁一共有三种状态:
读写锁的接口
#include
pthread_rwlock_t; //读写锁的类型
初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlock_t *attr);
读加锁操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
写加锁操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
解锁操作
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
销毁互斥锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
和互斥锁类似,只是当加锁操作被阻塞,阻塞的方式不同:互斥锁是通过将线程睡眠,自旋锁则是通过忙等待的方式。
自旋锁一般适用的场景是锁被其他线程短期持有(很快会被释放),而且等待该锁的线程不希望在阻塞期间被取消调度,因为这会带来一些开销。
概念
条件变量给多个线程提供了一个会和的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件的发生。
条件本身是由互斥量保护的,线程在改变条件状态前必须锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不同,常和互斥锁一起使用,在使用时,条件变量被用来阻塞一个线程,线程往往解开互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待
这个共享数据的线程。
条件变量的使用
#include
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
//第二个参数通常为空,且被忽略
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 阻塞等待一个条件变量,会阻塞, 传递的是加锁状态的锁 int
//该函数会阻塞等待条件变量(参数1满足),并释放已掌握的互斥锁,
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec* abstime);
//限时阻塞等待一个条件变量,在给定时间前条件没有满足返回ETIMEOUT
//abstime以与time系统调用相同意义的绝对时间的形式出现,0表示距离格林尼治时间1970.1.1 0秒
pthread_cond_signal(pthread_cond_t *cond);
//唤醒单个阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒所有阻塞在条件变量上的线程
int pthread_cond_destroy(pthread_cond_t *cond);
//只有当没有线程在该条件变量上等待时才能注销这个条件变量,否则返回EBUSY。
代码示例
#include
#include
#include
#include
#include
#include
pthread_mutex_t mutex;
pthread_cond_t cond;
char buff[128] = {0};
void *fun(void *arg)
{
int flg = *(int*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // mutex肯定是一个加锁状态的锁,以互斥方式将当前线程添加到等待条件变量的队列中
pthread_mutex_unlock(&mutex);
if(strncmp(buff,"end",3) == 0)
{
break;
}
printf("fun%d:%s\n",flag,buff);
memset(buff,0,128);
}
printf("fun over\n");
}
int main()
{
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
int flag1 = 1;
int flag2 = 2;
pthread_t id1, id2;
pthread_create(&id1, NULL, fun, (void*)(&flag1));
pthread_create(&id2, NULL, fun, (void*)(&flag2));
while(1)
{
printf("input: ");
fflush(stdout);
fgets(buff, 127, stdin);
if(strncmp(buff, "end", 3) == 0)
{
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
break;
}
else
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
pthread_join(id1, NULL);
pthread_join(id2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}