Linux之线程同步

线程同步

一、线程为什么要同步

1.线程的最大特点是资源的共享性,多个线程都可对共享资源操作;
2.线程操作共享资源的先后顺序不确定;
3.处理器对存储器的操作一般不是原子操作。

二、多线程之间有几个特殊的临界资源:

全局数据堆区数据文件描述符多线程之间共用。

三、处理方法

Linux下提供了多种方式来处理线程同步,最常用的是互斥锁条件变量信号量

3.1临界区

3.1.1概述

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。是保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么 在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

3.1.2 操作原语:

EnterCriticalSection()进入临界区
LeaveCriticalSection() 离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

3.1.3临界区的选定

临界区的选定因尽可能小,如果选定太大会影响程序的并行处理性能。

3.2互斥锁

3.2.1概述

互斥锁和临界区有些相似,完全控制临界资源,如果一个线程完成加锁操作,则其他线程无论如何都无法再完成加锁,也就无法对临界资源进行访问。

3.2.2锁机制

通过锁机制实现线程间的同步。
在这里插入图片描述

3.2.3操作原语
#include <pthread.h>
//1、初始化锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  //静态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);//动态分配
//2、加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//3、解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//4、销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.2.4举例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NLOOP 5000
//全局变量 可设置成静态 
int counter; //本例创建两个线程,各自把counter增加5000次
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;//静态分配
void *doit(void *);

int main(int argc, char **argv)
{
	pthread_t tidA, tidB;
	//创建两个线程
	pthread_create(&tidA, NULL, doit, NULL);
	pthread_create(&tidB, NULL, doit, NULL);
	//等待两个线程都终止 
	pthread_join(tidA, NULL);
	pthread_join(tidB, NULL);
	return 0;
}

void *doit(void *vptr)
{
	int i, val;
	for (i = 0; i < NLOOP; i++) 
	{
		//谁要操作全局变量谁拿锁  锁只有一把 
		//加锁 
		pthread_mutex_lock(&counter_mutex);
		val = counter;
		printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
		counter = val + 1;
		//解锁 
		pthread_mutex_unlock(&counter_mutex);
	}
	return NULL;
}

3.2死锁

1.同一个线程在拥有A锁的情况下再次请求获得A锁
2.线程一拥有A锁,请求获得B锁;线程二拥有B锁,请求获得A锁

3.3条件变量

3.3.1概述

1.与互斥锁不同,条件变量是用来等待而不是用来上锁的。
2.条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
3.条件变量分为两部分: 条件变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。
4.条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

3.3.2操作原语
#include<pthread.h>

//1、初始化条件变量。
pthread_cond_t cond = PTHREAD_COND_INITIALIER;//静态初始化
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);//动态初始化

/*2、等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)*/
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

//3、激活条件变量。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //(激活所有等待线程)解除所有线程的阻塞

//4、清除条件变量。无线程等待,否则返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond)
3.3.3举例(生产者消费者模型)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_produce = PTHREAD_COND_INITIALIZER;

struct msg{
	struct msg *next;
	int num;
};

struct msg *head;//头指针 

void *consumer(void *p)
{
	struct msg *mp;
	for(;;)
	{
		pthread_mutex_lock(&lock);
		while(NULL==head)
		{
			pthread_cond_wait(&has_produce,&lock);	
		} 
		mp = head;
		head = mp->next;
		pthread_mutex_unlock(&lock);
		//消费掉 
		printf("consumer %d \n",mp->num);
		free(mp);
		sleep(rand()%5); 
	}
}
void *producer(void *p)
{
	struct msg *mp;
	for(;;)
	{
		mp = malloc(sizeof(struct msg));
		mp->num = rand()%1000 + 1;
		printf("Producer %d\n",mp->num);
		pthread_mutex_lock(&lock);
		//头插法 
		mp->next = head;
		head = mp;
		pthread_mutex_unlock(&lock);
		//唤醒阻塞线程 
		pthread_cond_signal(&has_produce);
		sleep(rand()%2);
	}
}

int main()
{
	pthread_t pid,cid;
	pthread_create(&pid, NULL, producer, NULL);//生产者
	pthread_create(&cid, NULL, consumer, NULL);//消费者
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	return 0;
}

3.3信号量

3.3.1概述

1.“信号量”的作用就象是代码周围的门卫
2.二进制信号量:一种最简单的一种信号量,只有“0”,“1”两种取值
3.计数信号量:有更大的取值范围,一般用于希望有限个线程去执行一段给定的代码
4.如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"开头。
5.头文件<semaphore.h>

3.3.2操作原语
1.信号量的初始化

作用:对给定的信号量对象进行初始化

int sem_init(sem_t *sem,int pshared,unsigned value);
/*
sem: 要进行初始化的信号量对象
pshared:控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量
value:赋给信号量对象的一个整数类型的初始值
调用成功时 返回 0;
*/
2.释放信号量

作用:给信号量的值加上一个“1”,并通知其他等待线程。

int sem_post(sem_t *sem);
/*
sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值,调用成功时返回 0;
这是一个“原子操作”-即同时对同一个信号量做加“1”操作的两个线程是不会冲突的。信号量的值永远会正确地加上一个“2”,因为有两个线程试图改变它
*/
3.等待信号量

作用:从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法

int  sem_wait(sem_t *sem);
/*
sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值,调用成功时 返回 0;
也是一个“原子操作”。
*/
4.销毁信号量

作用:用完信号量后,对该信号量进行清理

int  sem_destroy(sem_t *sem);
/*
sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值,调用成功时 返回 0;
归还自己占有的一切资源,在清理信号量的时候如果还有线程在等待它,用户就会收到一个错误
*/
3.3.3举例
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#define NUM 5

int queue[NUM];
sem_t blank_number, product_number;

void *producer(void *arg)
{
	int p = 0;
	while (1) 
	{
		sem_wait(&blank_number);
		queue[p] = rand() % 1000 + 1;
		printf("Produce %d\n", queue[p]);
		sem_post(&product_number);
		p = (p+1)%NUM;//控制数组不越界 
		sleep(rand()%5);
	}
}
void *consumer(void *arg)
{
	int c = 0;
	while (1) 
	{
		sem_wait(&product_number);
		printf("Consume %d\n", queue[c]);
		queue[c] = 0;
		sem_post(&blank_number);
		c = (c+1)%NUM;
		sleep(rand()%5);	
	}
}
int main(int argc, char *argv[])
{
	pthread_t pid, cid;
	//初始化信号量 
	sem_init(&blank_number, 0, NUM);
	sem_init(&product_number, 0, 0);
	//创建线程 
	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, consumer, NULL);
	//回收线程 
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	//销毁信号量 
	sem_destroy(&blank_number);
	sem_destroy(&product_number);
	return 0;
}

参考:https://blog.csdn.net/xipiaoyouzi/article/details/52453274

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值