Linux线程同步(二)---互斥锁实现线程同步

一 why

先给自己打个广告,本人的微信公众号:嵌入式Linux江湖,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题。
在这里插入图片描述
在博客《Linux线程同步(一)—“初识篇”》中,介绍了为什么需要线程同步,从本篇博客开始介绍如何实现线程同步,本篇主要介绍的是互斥锁实现线程同步。

二 how

Linux提供一把互斥锁mutex(也称之为互斥量)。
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束后解锁。
资源还是共享的,线程间也还是竞争的,但通过锁将资源的访问变为互斥操作,而后与时间有关的错误也不会在产生了。
同一个时刻,只能有一个线程持有该锁,比如线程A拥有了这把锁,其他线程都无法拥有这把锁。只有获得这把锁的线程才可以访问临界区,其他没有获得这把锁的线程都无法访问临界区。在这里插入图片描述
互斥锁实质上是是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制,但是,并没有强制限定。
2.1 相关函数
互斥锁相关的函数主要有:初始化函数,销毁函数,加锁函数,以及解锁函数

初始化
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutex_attr *restrict attr)
参数1:传出参数,调用时应传&mutex
    restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。
参数2:互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).
    静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    动态初始化:局部变量应采用动态初始化。pthread_mutex_init(&mutex, NULL);
销毁
pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁
pthread_mutex_lock(pthread_mutex_t *mutex);
分析:
没有被上锁,当前线程会将这把锁锁上;
被锁上了,当前线程阻塞,锁被打开之后,线程解除阻塞。
pthread_mutex_trylock(pthread_mutex_t *mutex);
分析:
没有锁上:当前线程会给这把锁加锁
如果锁上了:不会阻塞,返回
解锁
pthread_mutex_unlock(pthread_mutex_t *mutex)
注意:
同时将阻塞在该锁上的所有线程全部唤醒

补充知识:
这里介绍一下restrict关键字的用法
restrict是C99新引入的关键字,只用于限定指针,表明本指针是访问一个数据对象的惟一且初始的方式。

三 demo

那么,如何使用上面的几个函数来解决《Linux线程同步(一)—“初识篇”》中的那个问题呢?我们可以在临界区的开始和结束的地方分别加锁,以及解锁即可。这样当两个线程分别访问这段临界区的时候,只有先获得锁的线程才能访问,没有获得锁的线程,会在"加锁"函数位置处等待,以便获得锁,这样线程A和线程B就是以顺序形式来访问临界区

void *pthfun(void *arg)
{
        int i = 0;

        pthread_mutex_lock(&test_mutex);

        for (i = 0; i < MAX_CNT; i++) {
                cnt++;
        }

        pthread_mutex_unlock(&test_mutex);

        return NULL;
}

完整函数如下:

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

#define MAX_CNT 10000

pthread_mutex_t test_mutex;
int cnt = 0;

void *pthfun(void *arg)
{
        int i = 0;

        pthread_mutex_lock(&test_mutex);

        for (i = 0; i < MAX_CNT; i++) {
                cnt++;
        }

        pthread_mutex_unlock(&test_mutex);

}

int main(int argc, char *argv[])
{
        pthread_t pth_id1, pth_id2;

        pthread_mutex_init(&test_mutex, NULL);

        pthread_create(&pth_id1, NULL, pthfun, NULL);
        pthread_create(&pth_id2, NULL, pthfun, NULL);

        pthread_join(pth_id1, NULL);
        pthread_join(pth_id2, NULL);

        if (cnt == 2 * MAX_CNT) {
                printf("It is the result as we think, cnt = %d!\n", cnt);
        } else {
                printf("Error, it is not the result as we think, cnt = %d\n!", cnt);
        }

        pthread_mutex_destroy(&test_mutex);

        return 0;
}

四 测试

编译运行加锁后的函数,发现每次运行结果都是20000
在这里插入图片描述

五 源码

	只是掌握互斥锁如何使用还不足够,我希望我们作为程序员要对自己要求严格一点,做到知其然并且知其所以然,那么互斥锁的源代码是如何实现的呢?
	pthread是POSIX thread,这些函数都是封装在了libpthread库中,使用时,我们只需include对应的头文件“pthread.h”即可调用这些函数。
	如果查看这些函数的声明,我们需要安装POSIX接口man手册(sudo apt-get install manpages-posix-dev),安装完成之后,我们即可用“man pthread_mutex_init”来查看此函数的声明部分。
	pthread的源代码是在GLIBC中的NPTL包中,NPTL,或称为 NativePOSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。与 LinuxThreads 一样,NPTL 也实现了一对一的模型。
大部分现代 Linux 发行版都预装了 LinuxThreads 和 NPTL,因此它们提供了一种机制来在二者之间进行切换。要查看您的系统上正在使用的是哪个线程库,请运行下面的命令:
getconf GNU_LIBPTHREAD_VERSION

在这里插入图片描述
如下表列出了一些流行的 Linux 发行版,以及它们所采用的线程实现的类型、glibc 库和内核版本。
在这里插入图片描述
知道了我们的虚拟机使用了是NPTL后,我们去glibc的官方网站下载glibc的源码:http://ftp.gnu.org/gnu/glibc/,下载合适的版本,比如我们下载的是glibc-2.34.tar.bz2,将这个压缩包传到虚拟机上,并解压,然后我们就可以到glibc-2.34/nptl路径下阅读NPTL的源码了
在这里插入图片描述
本篇先介绍到如何使用互斥锁,以及如何下载NPTL包,下一篇我们会阅读NPTL中的实现,来理解互斥锁的底层原理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值