Linux&Apue(0.3.1):锁的基本知识&用锁解决多线程访问修改共享资源问题

(一)锁的基本知识

在Linux&Apue(0.3.0)中我们用一个thread编程描述了多个子线程之间共享一个共享资源导致的问题。那么,我们怎么解决这个问题呢?

(1) 临界的定义

临界资源:一个资源会被不同的线程访问修改
临界区:临界资源访问修改相关的代码

(2) 互斥锁(pthread_mutex_t)

互斥锁:引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。(互斥锁并不占用任何资源)

2.1 互斥锁的创建

互斥锁创建有两种方法:静态方式&动态方式。

2.1.1 静态方式

静态方式:POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁。

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
2.1.2 动态方式

动态方式:采用pthread_mutex_init()函数来初始化互斥锁。

#include<pthread.h>		//头文件包含
 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)

mutexattr参数:用于指定互斥锁属性,如果为NULL则使用缺省值。(缺省值:默认选项(c),又称缺省值,是一种计算机术语,指在无决策者干预情况下,对于决策或应用软件、计算机程序的系统参数的自动选择。)

2.2 互斥锁的属性(四个值)

2.2.1 普通锁

普通锁(缺省值):当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

PTHREAD_MUTEX_TIMED_NP		//参数值
2.2.2 嵌套锁

嵌套锁:允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

PTHREAD_MUTEX_RECURSIVE_NP		//参数值
2.2.3 检错锁

检错锁:如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

PTHREAD_MUTEX_ERRORCHECK_NP		//参数值
2.2.4 适应锁

适应锁:仅等待解锁后重新竞争。

PTHREAD_MUTEX_ADAPTIVE_NP		//参数值

2.3 互斥锁的销毁

pthread_mutex_destroy ()函数:指向要销毁的互斥锁的指针,成功返回0。(其中含有:检查锁状态)

int pthread_mutex_destroy(pthread_mutex_t *mutex)

2.4 互斥锁的操作

锁操作主要包括:加锁,解锁,测试加锁。

2.4.1 加锁(阻塞)
int pthread_mutex_lock(pthread_mutex_t *mutex)
2.4.2 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
互斥锁属性解锁者
普通锁、适应锁进程内任何线程
检错锁必须是加锁者解锁才有效,否则返回EPERM
嵌套锁文档和实现要求必须由加锁者解锁
2.4.3 测试加锁(非阻塞)

测试加锁:与加锁操作类似。不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

int pthread_mutex_trylock(pthread_mutex_t *mutex)

(3) 死锁

死锁:如果多个线程要调用多个对象,则在上锁的时候可能会出现“死锁”。
在这里插入图片描述
简单来说:线程A持有锁1,线程B持有锁2。但是他们都在等着对方解锁。

3.1 死锁产生必要条件(四个)

必要条件解析
互斥某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束
占有且等待一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源
不可抢占别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
循环等待存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

3.2 死锁的应对方法(来源:百度百科)

这里只是粗略的提出,为了对死锁的应对有个基本的了解。具体下来的应对,这里就不做详细说明了。

3.2.1 死锁预防

死锁预防:方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。
优点:简单,直观
缺点:由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。

3.2.2 死锁避免

死锁避免:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源;如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。

3.2.3 死锁检测和解除

死锁检测和解除
①先检测:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
②解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。
优点:有可能使系统获得较好的资源利用率和吞吐量
缺点:实现上难度也最大。

(二)用锁解决多线程访问修改共享资源问题

在Linux&Apue(0.3.0)中,我们用一个简单例子进行了多线程之间编程,但是也出现了多线程之间共享了一个共享资源的问题。所以,我们现在来对之前的编程进行修改,用锁来解决问题。

(1)设置结构体,动态锁初始化

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thread_worker1(void *args);
void *thread_worker2(void *args);

//设置结构体,将传给子线程的参数放入
typedef struct worker_ctx_s
{
	int			shared_var;
	pthread_mutex_t		lock;
} worker_ctx_t;

int main(int argc, char **argv)
{
	worker_ctx_t	worker_ctx;
	pthread_t tid;			
	pthread_attr_t thread_attr;	
	//参数值初始化
	worker_ctx.shared_var=1000;
	//动态锁初始化
	pthread_mutex_init(&worker_ctx.lock,NULL);

(2) 设置线程属性

//一、设置线程属性
	//线程属性初始化
	if(pthread_attr_init(&thread_attr))
	{
 		printf("pthread_attr_init() failure: %s\n", strerror(errno));
 		return -1;
	}
	
	//线程堆栈大小设置
	if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
       	{
 		printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
 		return -2;
	}
	//设置线程关系
	if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
	{
	 	printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
	 	return -3;
	}

(3) 创建线程&主线程工作&销毁动态锁初始化

//二、创建线程	与之前不同,这里全设为了相分离
	//线程1
	pthread_create(&tid, &thread_attr, thread_worker1, &worker_ctx);
	printf("Thread worker1 tid[%ld] created ok\n", tid);
	//线程2
	pthread_create(&tid, &thread_attr, thread_worker2, &worker_ctx);
	printf("Thread worker2 tid[%ld] created ok\n", tid);


	//主线程工作
	while(1)
	{
		printf("Main/Control thread shared_var: %d\n", worker_ctx.shared_var);
		 sleep(10);
	}
	//销毁动态锁初始化
	pthread_mutex_destroy(&worker_ctx.lock);

}

(4) 子线程工作&加锁,解锁操作

void *thread_worker1(void *args)
{
       	worker_ctx_t	*ctx=(worker_ctx_t*)args;
	if( !args )
	{
		printf("%s() get invalid arguments\n", __FUNCTION__);
		pthread_exit(NULL);	//若用exit会退出进程
	}

	printf("Thread workder 1 [%ld] start running...\n", pthread_self());
	//子线程工作
	while(1)
	{	
		//加锁(阻塞)
		pthread_mutex_lock(&ctx->lock);

		printf("+++: %s before shared_var++: %d\n", __FUNCTION__, ctx->shared_var);
		ctx->shared_var ++;
		sleep(2);
		printf("+++: %s after sleep shared_var: %d\n", __FUNCTION__, ctx->shared_var);
		//解锁
		pthread_mutex_unlock(&ctx->lock);
	}
	printf("Thread workder 1 exit...\n");
	return NULL;
}
void *thread_worker2(void *args)
{
       	worker_ctx_t	*ctx=(worker_ctx_t*)args;
	if( !args )
	{
		printf("%s() get invalid arguments\n", __FUNCTION__);
		pthread_exit(NULL);
	}
	//子线程工作
	printf("Thread workder 2 [%ld] start running...\n", pthread_self());
	while(1)
	{
		//测试加锁(非阻塞)
		if(0!=pthread_mutex_trylock(&ctx->lock))
		{
			continue;
		}
		printf("---: %s before shared_var++: %d\n", __FUNCTION__, ctx->shared_var);
		ctx->shared_var++;
		sleep(2);
		printf("---: %s after sleep shared_var: %d\n", __FUNCTION__, ctx->shared_var);
		//解锁
		pthread_mutex_unlock(&ctx->lock);
		sleep(1);
	}
	printf("Thread workder 2 exit...\n");

	return NULL;
}

(5)运行结果

在这里插入图片描述
简析:
worker1:befor 1000,after 1001。
worker2:befor 1001,after 1002。
这说明在worker1获取共享数据的时候,worker2没法获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值