线程的互斥与同步
什么是互斥?什么是同步?
互斥:一个资源一次只能被一个访问者使用(保证访问数据,唯一访问性)
举个例子:你去上厕所,门一开,人进去,门锁上,在你上厕所期间别人不能打扰
同步:当多个访问者一起工作时并对每个访问者访问的时序有一定限制(保证访问数据,时序访问性)
举个例子:你上完厕所,刚出来,本来应该由别人了,但是你此时又进去,你反复的进去出来,导致别人都上不了厕所。因此同步就是保证时序性的,你出来了,应该去后面排队。
为什么要同步与互斥?
1.在运行多个任务,都需要访问同一种资源-----------竞争
2.多个任务之间有依赖关系,某个任务的运行状态依赖于另一个任务
同步互斥就是解决这类问题的。
互斥和同步的联系?
同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说互斥是两个任务之间不可以同时运行,他们会互相排斥,必须等待一个执行完再执行,而同步也是不能同时运行,但是必须要按照某种次序来运行。但是互斥不限制任务的执行顺序。(互斥任务是无序的,同步任务是有序的)
线程如何实现互斥
互斥量 :是一个可以处于两态之一的变量,解锁和加锁。用来保护临界资源的。当你访问临界资源的时候,先要申请锁,如果别人还没释放你就被阻塞,如果没人用你就可以加锁,这样就很有效的保证临界资源一段时间内只能被一个人访问。
互斥量接口如下:
互斥量使用注意事项:
1.初始化互斥量(程序起始)
2.加锁操作(进入临界区之前)
3.解锁操作(出临界区之后)注意:你加锁了,在有可能退出的地方都要解锁,否则会出现死锁问题
4.销毁互斥量(程序结束前)
下面看一个例子,购票ticket--,不是一个原子操作,有可能在sleep的时候被切出去,造成错误判断的问题,因此进入临界区的时候要进行加锁。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 100;
pthread_mutex_t mutex;
void * get_ticket(void *arg)
{
int id = (int)arg;
while (1)
{
pthread_mutex_lock(&mutex);//进入临界区加锁
if (ticket>0)
{
usleep(100);
ticket--;
printf("thread :%d get a ticket :%d\n", id, ticket);
pthread_mutex_unlock(&mutex);//解锁
}
else
{
pthread_mutex_unlock(&mutex);//在有可能退出的地方,都要解锁,否则会造成死锁不能退出
pthread_exit(NULL);
}
}
}
int main()
{
pthread_t tid[4];
pthread_mutex_init(&mutex, NULL);//初始化互斥锁
int i, ret;
for (i = 0; i<4; i++)
{
ret = pthread_create(&tid[i], NULL, get_ticket, (void*)i);
if (ret != 0)
{
printf("pthread_creat error!!\n");
return -1;
}
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);
pthread_mutex_destroy(&mutex);//销毁互斥锁
return 0;
}
线程如何实现同步
1.条件变量:是用来等待某一条件的发生
主要包括以下两个动作:
等待“条件变量的条件成立”而挂起
使“条件成立”(给出条件成立信号)
为了防止竞争,条件变量一般和互斥量搭配使用
怎样理解:1.条件变量也应当受到保护因此要使用互斥锁(线程在改变条件变量状态之前先上锁)2.如果只使用互斥锁,其他线程到这个程序上来就会判断是否上锁,如果上锁了就等待,这个时候可能会有很多线程都来判断等待,然后阻塞在这里,当调用锁的线程释放锁后,那么被阻塞的线程又会来抢夺这个资源。而加上条件变量,条件不满足可以让阻塞的线程等待在条件变量上,条件满足会唤醒等待在条件变量上的线程,从而弥补了互斥量的不足。(总结条件变量与互斥量一起使用,允许线程以无竞争的方式等待条件的发生)
条件变量相关接口:
生产者消费者模型的实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
int goods = 0;
pthread_cond_t full;//生产
pthread_cond_t empty;//消费
pthread_mutex_t mutex;
void *consumer(void *arg)
{
int id = (int)arg;
//我是一个消费者
while(1) {
pthread_mutex_lock(&mutex);
while(goods== 0) {
pthread_cond_wait(&empty, &mutex);//pthread_con_wait干两件事:原子操作 1. 释放锁 2.等待
}
usleep(100000);
goods= 0;
printf("consumer %d get an apple!!\n", id);
pthread_cond_signal(&full);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *producer(void *arg)
{
int id = (int)arg;
while(1) {
pthread_mutex_lock(&mutex);
while(goods== 1) {//思考:此处为什么不是if判断,而是while 原因:因为在signal唤醒wait之间,有另一个生产者可能已经生产了,因此此时应继续判断
pthread_cond_wait(&full, &mutex);
}
goods = 1;
printf("producer %d put an apple!!\n", id);
//唤醒等待在cond上的线程
pthread_cond_signal(&empty);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_t tid;
int ret, i;
pthread_cond_init(&full, NULL);
pthread_cond_init(&empty, NULL);
pthread_mutex_init(&mutex, NULL);
for (i = 0; i < 4; i++) {
ret = pthread_create(&tid, NULL, producer, (void*)i);
}
for (i = 0; i < 4; i++) {
ret = pthread_create(&tid, NULL, consumer, (void*)i);
}
pthread_join(tid, NULL);
pthread_cond_destroy(&empty);
pthread_cond_destroy(&full);
pthread_mutex_destroy(&mutex);
return 0;
}
2.POSIX信号量:有等待队列的计数器(记录临界资源的个数)
目的:为了同步操作,达到无冲突的访问共享资源的目的POSIX
POSIX信号量接口如下:
生产者消费者代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
sem_t producer, consumer;
int noodles = 1;
void* thr_producer(void* arg)
{
while (1)
{
sem_wait(&producer);
printf("producer put a noodles!!!\n");
noodles=1;
sleep(1);
sem_post(&consumer);
}
return NULL;
}
void* thr_consumer(void* arg)
{
while (1)
{
sem_wait(&consumer);
printf("consumer get a noodles!!!\n");
noodles=0;
sem_post(&producer);
}
return NULL;
}
int main()
{
int ret;
pthread_t tid1, tid2;
ret = sem_init(&producer, 0, 0);
ret = sem_init(&consumer, 0, 1);
if (ret<0)
{
printf("sem init error!!\n");
return -1;
}
ret == pthread_create(&tid1, NULL, thr_producer, NULL);
if (ret != 0)
{
printf("pthread1 create error!!\n");
return -1;
}
ret == pthread_create(&tid2, NULL, thr_consumer, NULL);
if (ret != 0)
{
printf("pthread2 create error!!\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&producer);
sem_destroy(&consumer);
return 0;
}
(3)读写锁
在一些多线程场景下,有些公共的数据修改的机会比较少,相比较修改,我们读的机会反而更高,为这段代码加锁,会极大的降低我们程序的效率,而读写锁就是专门来处理这种问题的。
读写锁特点:写独占,读共享
读写锁其实也是一种自旋锁,获取不到锁,我不挂起,频频回头,一直尝试加锁(自旋锁应用于你确定加锁不会耗费很长的时间)
读写锁的接口如下:
读写者模型:三种关系,两种角色,一个交易场所 (我们考虑读者优先)
读者与读者 : 共享(也可以说没啥关系)
读者与写者 : 互斥与同步(读者写着不能同时进行,一旦有写者,则后续读者将会等待,唤醒时优先考虑读者)
写者与写者 :互斥
代码实现如下:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
char* ptr = "i am blockboard!!!";
pthread_rwlock_t rwlock;
void* thr_write(void * arg)
{
while (1)
{
pthread_rwlock_wrlock(&rwlock);
printf("draw ....\n");
sleep(1);
pthread_rwlock_wrlock(&rwlock);
usleep(1000);
}
return NULL;
}
void* thr_read(void *arg)
{
int id = (int)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
printf("i am:%d public~~,[%s] is beautiful!!\n", id, ptr);
sleep(1);
printf("wake up%d!!\n", id);
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2, tid3;
int ret;
pthread_rwlock_init(&rwlock, NULL);
ret = pthread_create(&tid1, NULL, thr_write, (void*)1);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
ret = pthread_create(&tid3, NULL, thr_read, (void*)3);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
ret = pthread_create(&tid2, NULL, thr_read, (void*)2);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
上一篇: 线程的同步与互斥