Linux系统编程09---线程同步

作者介绍

张伟伟,男,西安工程大学电子信息学院,2019级硕士研究生,张宏伟人工智能课题组。
微信公众号:可随时查阅,搜索—张二牛的笔记,内容会分类上传。
研究方向:机器视觉与人工智能。
电子邮件:2430290933@qq.com
电子邮件:zhangweiweicpp@163.com

学习目标

- 熟练掌握互斥量的使用
- 说出什么叫死锁以及解决方案
- 熟练掌握读写锁的使用
- 熟练掌握**条件变量**的使用
- 理解条件变量实现的生产消费者模型
- 理解信号量实现的生产消费者模型

1.互斥锁的使用步骤

1. 创建一把互斥锁
	pthread_mutex_t mutex;
2. 初始化互斥锁
	pthread_mutex_init(&mutex);---相当于mutex=1
3. 在代码中寻找共享资源(也称为临界区)
	pthread_mutex_lock(&mutex);  -- mutex = 0[临界区代码]
4.  pthread_mutex_unlock(&mutex); -- mutex = 1
5. 释放互斥锁资源
	pthread_mutex_destroy(&mutex);

2 死锁

2.1 死锁的产生原因

死锁并不是linux提供给用户的一种使用方法,而是由于用户使用互斥锁不当引起的一种现象。

1.自己锁自己 :注意点:线程在异常退出时也需要解锁
在这里插入图片描述
2.加锁不释放
在这里插入图片描述

2.2 死锁如何解决

不同时两把锁,线程在异常退出时也需要解锁。

让线程按照一定的顺序去访问共享资源
	1.在访问其他锁的时候,需要先将自己的锁解开
	2.避免使用嵌套的锁,让线程按照一定的顺序去加锁
	3.调用pthread_mutex_trylock,如果加锁不成功会立刻返回

3. 读写锁(是一把锁,读时共享写时独占)读写互斥

什么是读写锁
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
当读写同时等待锁的时候写的优先级高

情景:
	线程A拥有读锁, 线程B请求读锁

3.1读写锁的函数

定义一把读写锁
	pthread_rwlock_t rwlock;
	初始化读写锁
	int pthread_rwlock_init(                                
pthread_rwlock_t *restrict rwlock, 
const pthread_rwlockattr_t *restrict attr);
	函数参数
	rwlock-读写锁
	attr-读写锁属性,传NULL为默认属性
	销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);        
	加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);              
	尝试加读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
	加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
	尝试加写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
	解锁
int pthread_rwlock_unlock(&pthread_rwlock_t *rwlock);

3.2 读写锁的使用步骤

1.先定义一把锁:
	 pthread_rwlock_t rwlock;
2.初始化读写锁
 	pthread_rwlock_init(&rwlock, NULL);

----子线程------	
3.加锁
 	pthread_rwlock_rdlock(&rwlock);
 	pthread_rwlock_wrlock(&rwlock);
 	共享资源出现的位置
4.解锁
 	pthread_rwlock_unlock(&rwlock);
---子线程-------	

5.释放锁
 	pthread_rwlock_destroy(&rwlock);

3.3 读写锁的代码案例

  • 线程回调函数传传参方法。在这里插入图片描述
//读写锁测试程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

int number = 0;

//定义一把读写锁
pthread_rwlock_t rwlock;

//写线程回调函数
void *thread_write(void *arg)
{
	int i = *(int *)arg;

	int cur;

	while(1)
	{
		//加写锁
		pthread_rwlock_wrlock(&rwlock);

		cur = number;
		cur++;
		number = cur;	
		printf("[%d]-W:[%d]\n", i, cur);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand()%3);
	}
}

//读线程回调函数
void *thread_read(void *arg)
{
	int i = *(int *)arg;
	int cur;

	while(1)
	{
		//加读锁
		pthread_rwlock_rdlock(&rwlock);

		cur = number;
		printf("[%d]-R:[%d]\n", i, cur);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand()%3);
	}	
}

int main()
{
	int n = 8;
	int i = 0;
	int arr[8];
	pthread_t thread[8];

	//读写锁初始化
	pthread_rwlock_init(&rwlock, NULL);

	//创建3个写子线程
	for(i=0; i<3; i++)
	{
		arr[i] = i;
		pthread_create(&thread[i], NULL, thread_write, &arr[i]);
	}

	//创建5个读子线程
	for(i=3; i<n; i++)
	{
		arr[i] = i;
		pthread_create(&thread[i], NULL, thread_read, &arr[i]);
	}

	//回收子线程
	int j = 0;
	for(j=0;j<n; j++)
	{
		pthread_join(thread[j], NULL);
	}
	//释放锁
	pthread_rwlock_destroy(&rwlock);

	return 0;
}

4. 介绍生产者和消费者模型

4.1 链表操作

链表的使用
节点,指向,偏移

头插法,移动,删除,head为共享资源。
防止循环空转: 阻塞等待,解锁,然后加锁。
生产者线程 | 消费者线程
在这里插入图片描述
在这里插入图片描述

4.2 条件变量(与互斥锁配合使用,给多线程提供一个回合的场所)

pthread_cond_wait()
pthread_cond_signal()

(1) 条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
(2) 使用互斥量保护共享数据;
(3) 使用条件变量可以使线程阻塞, 等待某个条件的发生, 当条件满足的时候解除阻塞.
(4) 条件变量的两个动作:
---------条件不满足, 阻塞线程
---------条件满足, 通知阻塞的线程解除阻塞, 开始工作

4.3 条件变量相关函数

pthread_cond_t  cond;
	定义一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
                   const pthread_condattr_t *restrict attr);
	函数描述:初始化条件变量
	函数参数: 
		cond: 条件变量
		attr: 条件变量属性, 通常传NULL
	函数返回值:成功返回0, 失败返回错误号
int pthread_cond_destroy(pthread_cond_t *cond);
	函数描述: 销毁条件变量
	函数参数: 条件变量
	返回值: 成功返回0, 失败返回错误号
int pthread_cond_wait(pthread_cond_t *restrict cond,
                   pthread_mutex_t *restrict mutex);
	函数描述: 条件不满足, 引起线程阻塞并解锁;
          条件满足, 解除线程阻塞, 并加锁
	函数参数:
		cond: 条件变量
		mutex: 互斥锁变量
	函数返回值: 成功返回0, 失败返回错误号
int pthread_cond_signal(pthread_cond_t *cond);
	函数描述: 唤醒至少一个阻塞在该条件变量上的线程
	函数参数: 条件变量
	函数返回值: 成功返回0, 失败返回错误号

4.4 使用条件变量实现生产者和消费者模型

//使用条件变量实现生产者和消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义一把锁
pthread_mutex_t mutex;

//定义条件变量
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P[%d]:[%d]\n", n, pNode->data);

		//加锁
		pthread_mutex_lock(&mutex);

		pNode->next = head;
		head = pNode;

		//解锁
		pthread_mutex_unlock(&mutex);

		//通知消费者线程解除阻塞
		pthread_cond_signal(&cond);

		sleep(rand()%3);
	}
}

//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		if(head==NULL)
		{
			//若条件不满足,需要阻塞等待
			//若条件不满足,则阻塞等待并解锁;
			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
			pthread_cond_wait(&cond, &mutex);
		}

		if(head==NULL)
		{
			//解锁
			pthread_mutex_unlock(&mutex);	
			continue;
		}

		printf("C[%d]:[%d]\n", n, head->data);	
		pNode = head;
		head = head->next;

		//解锁
		pthread_mutex_unlock(&mutex);

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	int i = 0;
	pthread_t thread1[5];
	pthread_t thread2[5];

	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL);

	//条件变量初始化
	pthread_cond_init(&cond, NULL);

	int arr[5];
	for(i=0; i<5; i++)
	{
		arr[i]= i;
		//创建生产者线程
		ret = pthread_create(&thread1[i], NULL, producer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}

		//创建消费者线程
		ret = pthread_create(&thread2[i], NULL, consumer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}
	}

	//等待线程结束
	for(i=0; i<5; i++)
	{
		pthread_join(thread1[i], NULL);
		pthread_join(thread2[i], NULL);
	}

	//释放互斥锁
	pthread_mutex_destroy(&mutex);

	//释放条件变量
	pthread_cond_destroy(&cond);

	return 0;
}

4.5 条件变量使用总结

1、定义条件变量
	 pthread_cont_t cond;
2.初始化条件变量
	pthread_cond_init(&cond, NULL);
3.在生产者线程中调用:
	pthread_cond_signal(&cond);
4.在消费者线程中调用:
	pthread_cond_desroy(&cond);
5.主线程释放条件变量

多个生产者和多个消费者程序在执行的时候core掉 的原因分析:

假若只有一个生产者生产了一个节点,此时会调用pthread_cond_signal通知消费者线程,此时若有多个消费者线程被唤醒了,则最终只有一个消费者获得锁,然后进行消费,此时将head置位NULL,然后其余的几个消费者线程只会有一个线程获得锁,然后读取head的内容就会core掉。

在使用条件变量的线程中,能够引起线程阻塞的地方有两个:
1.在条件变量出引起阻塞,这个阻塞会被pthread_cond_signal解除阻塞。
2.互斥锁也会使线程引起阻塞,其他县城解锁会使该线程解除阻塞。

5.信号量(相当于加强版的互斥锁)

互斥锁:进一个出一个
信号量:多个同时
车库为共享资源 车为线程 管理员相当于锁
在这里插入图片描述

5.1 信号量相关函数(互斥锁加条件变量)

定义信号量 sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);	
	函数描述: 初始化信号量
	函数参数:
	sem: 信号量变量
	pshared: 0表示线程同步, 1表示进程同步
	value: 最多有几个线程操作共享数据
	函数返回值:成功返回0, 失败返回-1, 并设置errno值
int sem_wait(sem_t *sem);  **(加锁)**
	函数描述: 调用该函数一次, 相当于sem--, 当sem为0的时候, 引起阻塞
	函数参数: 信号量变量
	函数返回值: 成功返回0, 失败返回-1, 并设置errno值
int sem_post(sem_t *sem);  **(解锁)**
	函数描述: 调用一次, 相当于sem++
	函数参数: 信号量变量
	函数返回值: 成功返回0, 失败返回-1, 并设置errno值
int sem_trywait(sem_t *sem);
	函数描述: 尝试加锁, 若失败直接返回, 不阻塞
	函数参数: 信号量变量
	函数返回值: 成功返回0, 失败返回-1, 并设置errno值
int sem_destroy(sem_t *sem);
	函数描述: 销毁信号量
	函数参数: 信号量变量
	函数返回值: 成功返回0, 失败返回-1, 并设置errno值

在这里插入图片描述

5.2 信号量实现生产者与消费者

//使用信号量实现生产者和消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义信号量
sem_t sem_producer;
sem_t sem_consumer;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P:[%d]\n", pNode->data);

		//加锁
		sem_wait(&sem_producer); //--

		pNode->next = head;
		head = pNode;

		//解锁
		sem_post(&sem_consumer);  //相当于++

		sleep(rand()%3);
	}
}

//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//加锁
		sem_wait(&sem_consumer); //相当于--
		
		printf("C:[%d]\n", head->data);	
		pNode = head;
		head = head->next;

		//解锁
		sem_post(&sem_producer); //相当于++

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	pthread_t thread1;
	pthread_t thread2;

	//初始化信号量
	sem_init(&sem_producer, 0, 5);
	sem_init(&sem_consumer, 0, 0);

	//创建生产者线程
	ret = pthread_create(&thread1, NULL, producer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//创建消费者线程
	ret = pthread_create(&thread2, NULL, consumer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//等待线程结束
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);

	//释放信号量资源
	sem_destroy(&sem_producer);
	sem_destroy(&sem_consumer);

	return 0;
}

5.3 总结

1. 定义信号量变量
	sem_t sem1;
	sem_t sem2;
2. 初始化信号量
	 sem_init(&sem1, 0, 5);
	 sem_init(&sem2, 0, 5);
3. 加锁
	sem_wait(&sem1);
	sem_post(&sem2);
	
	sem_wait(&sem2);
	sem_post(&sem1);
4. 释放资源
	sem_destroy(&sem1);
	sem_destroy(&sem2);


---------回顾linux系统编程课程------------

1. linux常用命令
	linux 目录结构一级常见目录的作用
	ls cp mv rm rmdir mkdir touch chmod chgrp chown which whoami tar zip rar find grep

2. vim的使用:命令模式  编辑模式  末行模式
	gcc的工作流程
	库的制作与使用
	动态库的加载的时候报错问题的处理:export LD_LIBRAY_PATH=$LD_LIBRARY_PATH:./
	
3.makefile 和 gdb 调试
	目标:依赖
	命令
	函数 变量  伪目标
	编译的时候加-g ,否则不能用gdb调试
	理解什么是文件描述符
	
4.文件IO
	open read write close lseek
	lstat stat
	dup dup2 fcntl
	opendir readdir closedir
	
5.进程相关
	重在理解进程和程序的概念
	fork函数
	execl  execlp函数
	父进程中调用wait和waitpid,参数和返回值。
	
6.进程间通信
	pipe fifo mmap
	
7.信号
	信号的基本概念
	信号的处理机制:内核
	信号的特点
	信号的四要素
	信号相关函数: kill alarm settimer raise abort 
	信号注册函数:signal sigaction(高速内核)
	信号的处理过程:
	SIGCHLD信号:该信号产生的条件及作用(告诉内核回收子进程)**回顾linux系统编程课程**
信号集相关:
	阻塞信号集合未决信号集的关系
	信号不支持排队
	sigset_t set
	sigemptyset sigaddset sigfillset sigelset sigprocmask sigsmember sigpending
	
8.守护进程与线程
	创建守护进程的模型:(代码看看)
	线程:
		线程的基本概念
		线程和进程的比较(共享哪些,不能够共享哪些)
		线程相关函数:
			pthread_create 
			pthread_exit  
			pthread_join 
			pthread_detach 
			pthread_cancel 
			pthread_testcancel
9.线程同步:
	互斥锁的使用步骤
	读写锁的使用步骤
	条件变量的使用步骤
	
		
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值