欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程的互斥与同步

程序员文章站 2022-05-22 11:21:51
...

什么是互斥?什么是同步?

互斥:一个资源一次只能被一个访问者使用(保证访问数据,唯一访问性)

举个例子:你去上厕所,门一开,人进去,门锁上,在你上厕所期间别人不能打扰

同步:当多个访问者一起工作时并对每个访问者访问的时序有一定限制(保证访问数据,时序访问性)

举个例子:你上完厕所,刚出来,本来应该由别人了,但是你此时又进去,你反复的进去出来,导致别人都上不了厕所。因此同步就是保证时序性的,你出来了,应该去后面排队。

为什么要同步与互斥?

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;
	}