01Linux下C语言锁的学习之Linux下的互斥锁

01Linux下C语言锁的学习之Linux下的互斥锁

概述:
为什么需要锁,因为当我们多个线程访问全局变量时,同时操作的话可能存在问题,例如小明和小红同时去拿家里的银行账号里的钱,小明看到时为100块,小红也是,但是小明先拿到,若不加锁,那么小红再拿100的时候就会报错。所以必须在小明操作的时候限制小红操作。

不考虑相应初始化下,实际上所有的锁的使用步骤都是上锁,操作,解锁。

锁的粒度:即你上锁和解锁中间的操作,步骤越少越好。

注意:下面的互斥锁和题目一样,只是针对于Linux下使用,Windows下有自己相应的函数和锁,并且有临界点。当然,如果熟悉锁的人一般不使用这种了,例如我C++开发时一般使用guard这种,方便安全。

1 与Linux下的互斥锁相关的函数介绍
1)pthread_mutex_init函数

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
/*
	参1:传出参数,调用时应传&mutex。	
	参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 

	1).静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。例如pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
	2).动态初始化:局部变量应采用动态初始化。例如pthread_mutex_init(&mutex, NULL)。这是平时常用的方法。调用该初始化函数就是动态初始化。

	restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。
	例如:int a=3; 
		int *restrict p = &a;
		int *q=p;
		那么
		*q=5//error
*/

2)pthread_mutex_destroy函数

int pthread_mutex_destroy(pthread_mutex_t *mutex);
/*
	销毁一个互斥锁。
*/

3)pthread_mutex_lock函数

int pthread_mutex_lock(pthread_mutex_t *mutex);
/*
	加锁。
	若得不到锁,该函数会阻塞,即该函数具有阻塞功能。
*/

4)pthread_mutex_unlock函数

int pthread_mutex_unlock(pthread_mutex_t *mutex);
/*
	解锁。
	unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
*/

5)pthread_mutex_trylock函数

int pthread_mutex_trylock(pthread_mutex_t *mutex);
/*
	尝试解锁。不会阻塞,直接返回错误号,例如EBUSY,具体查看man手册。
*/

感觉没什么好讲的。下面直接看例子即可,注意看主线程和子线程的上锁和解锁中间的共享资源即可。这里的共享资源是stdout屏幕。

2 代码例子

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//互斥锁
pthread_mutex_t mutex;

void *tfn(void *arg){
    srand(time(NULL));
    while (1) {
    	//上锁
		pthread_mutex_lock(&mutex);
        printf("hello ");
        sleep(rand() % 3);	/*模拟长时间操作共享资源,导致cpu易主执行其它*/
        printf("world\n");
        sleep(rand() % 3);
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    
    return NULL;
}

int main(void){
    pthread_t tid;
    srand(time(NULL));//随机种子
	
	//1 初始化
	int ret = pthread_mutex_init(&mutex, NULL);
	if(ret < 0){
		printf("pthread_mutex_init failed.\n");
		return -1;
	}
	
    pthread_create(&tid, NULL, tfn, NULL);
    while (1) {
		//2 上锁
		pthread_mutex_lock(&mutex);
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
        //3 解锁
        pthread_mutex_unlock(&mutex);
    }

	//等待子进程结束回收资源
    pthread_join(tid, NULL);
	
	//4 销毁互斥锁
	ret = pthread_mutex_destroy(&mutex);
	if(ret < 0){
		printf("pthread_mutex_destroy failed.\n");
		return -1;
	}
	
    return 0;
}							

3 互斥锁的两种死锁可能
1)一把锁,一个共享全局变量的情况
看下面代码:

		//上锁
		pthread_mutex_lock(&mutex);
		//pthread_mutex_lock(&mutex);也会造成死锁。
        printf("HELLO ");//或者其它函数
        //解锁
        pthread_mutex_unlock(&mutex);

第一种的死锁又分两种情况:

  • 如果当printf()函数出现错误直接返回的话,那么就无法往下执行解锁,其它线程一直阻塞等待,而锁无法得到释放就造成了死锁,谁也获取不了,程序干巴巴的运行着却什么也做不了。
  • 如果你在操作里面再一次上锁也会造成死锁,因为自己锁住后又上锁,导致卡死在操作里面。

2)两把锁,两个共享全局变量的情况
例如,线程T1拥有A锁,请求获取B锁;线程T2拥有B锁,请求获取A锁。
看图分析:
在这里插入图片描述
上图看到,实际上和第一种的第二个情况类似。
当T1线程拥有A锁后,再去申请B锁,那么T1此时阻塞等待B锁的释放;而T2此时拥有B锁,同样去申请A锁,那么T2此时阻塞等待A锁的释放。这样就导致,T1和T2线程均阻塞并且无法互相释放A和B两把互斥锁,导致死锁。

注意:以T1线程为例,T1线程申请B锁必定是在A上锁的情况下操作,否则就不叫T1线程拥有A锁。T2线程同理。
不过开发时很少用到两种全局共享变量,一个已经足够用了。

4 总结互斥锁
一般这种互斥锁在嵌入式Linux的C下用得比较多,C++开发有自己的锁例如std::lock_guard以及用于高级用例的std::unique_lock。
所以建议C++开发的话不使用这种锁,Linux下C使用这种锁的话必须细心观察,在中间操作的地方防止有可能出现死锁的情况和再加锁。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值