当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,若每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性概念,同样地,若变量是只读的,多个线程同时读取该变量也不会有一致性问题,但是当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数值。
线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行,线程同步的方法有四种:
概念
互斥锁只有两种状态,加锁状态和解锁状态,
如果一个线程对已经处于加锁状态互斥锁进行加锁操作,则枷锁操作会阻塞,直到正在对互斥锁加锁状态线程进行解锁操作。
互斥锁的接口
#include
pthread_mutex_t //互斥锁的类型
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
加锁操作
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);
互斥锁的使用
示例:模拟连个线程竞争一个打印机,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 pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
int pthread_cond_destroy(pthread_cond_t *cond);
代码示例
#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);
}