Linux互斥锁封装:保障多线程同步安全 (linux互斥锁封装)

随着计算机硬件的高速发展和软件复杂程度的增加,多线程编程成为了现代操作系统和应用程序开发中的必备技术。同时,多线程编程也带来了一些新的问题,例如多线程之间的资源竞争、数据同步等。为了解决这些问题,操作系统提供了一些线程同步的机制,比如互斥锁、信号量、条件变量等。本文主要介绍Linux互斥锁的封装,以保障多线程同步安全。

1. 互斥锁与多线程同步

互斥锁是一种线程同步的机制,它可以确保在任意时刻只有一个线程访问共享资源,从而避免多个线程同时访问共享资源而导致的数据错误。具体来说,当一个线程需要访问共享资源时,它先尝试获取互斥锁,如果锁已经被其他线程获取,则当前线程会阻塞等待锁被释放。当锁被释放后,当前线程再次尝试获取锁,如果获取成功,则该线程可以开始访问共享资源。

互斥锁的使用需要遵守以下几个原则:

1) 尽量缩小互斥锁保护的数据范围,避免不必要的线程阻塞和锁竞争;

2) 确保每个访问共享资源的线程都正确地获取和释放互斥锁;

3) 避免死锁或饥饿问题,即在持有锁的情况下不能阻塞其他线程或导致其他线程无法获取锁。

2. Linux互斥锁的封装

在Linux系统下,互斥锁的使用需要依赖pthread.h头文件和相关库函数。然而,直接使用互斥锁有时会存在一些问题,例如使用不当容易导致死锁或饥饿问题,而且代码冗长,不便于维护。

为了解决这些问题,我们可以对互斥锁进行封装,以提供更为安全、简单、易用的接口。以下是一个简单的互斥锁封装示例:

“`c

#include

#include

class Mutex {

public:

Mutex() {

pthread_mutex_init(&mutex_, NULL);

}

~Mutex() {

pthread_mutex_destroy(&mutex_);

}

void lock() {

pthread_mutex_lock(&mutex_);

}

void unlock() {

pthread_mutex_unlock(&mutex_);

}

private:

pthread_mutex_t mutex_;

};

“`

上述代码定义了一个名为Mutex的类,这个类封装了一个互斥锁pthread_mutex_t mutex_。类的构造函数和析构函数分别对互斥锁进行初始化和销毁。类中还定义了lock()和unlock()方法,用于获取和释放互斥锁。

使用上述封装后的互斥锁,我们可以更加方便地实现多线程同步。例如,以下代码展示了一个线程安全的计数器实现:

“`c

#include

#include

#include “Mutex.h”

int count = 0;

Mutex g_mutex;

void worker() {

for (int i = 0; i

g_mutex.lock();

++count;

g_mutex.unlock();

}

}

int mn() {

std::thread t1(worker);

std::thread t2(worker);

t1.join();

t2.join();

std::cout

return 0;

}

“`

上述代码中,我们定义了一个全局的计数器count和一个Mutex对象g_mutex,并创建了两个线程执行worker()函数。在worker()函数中,我们使用互斥锁保护计数器count的访问,以避免多线程访问的竞争和错误。

3.

相关问题拓展阅读:

Linux下各种锁的理解和使用及总结解决epoll惊群问题(面试常考)-

锁出现的原因

临举态界资源是什么: 多线程执行流所共享的资源

锁的作用是什么, 可以做原子操作, 在多线程中针对临界资源的互斥访问… 保证一个时刻只有一个线程可以持有锁对于临界资源做修改操作…

任何一个线程如果需要修改,向临界资源做写入操作都必须持有锁,没有持有锁就不能对于临界资源做写入操作.

锁 : 保证同一时刻只能有一个线程对于临界资源做写入操作 (锁地功能)

再一个直观地代码引出问题,再从指令集的角度去看问题

上述一个及其奇怪的结果,这个结果每一次运行都可能是不一样的,Why ? 按照我们本来的想法是每一个线程 +结果肯定应该是呀,可以就是达不到这个值

为何? (深入汇编指令来看) 一定将过程放置到汇编指令上去看就可以理解这个过程了.

a++; 或者 a += 1; 这些操作的汇编操作是几个步骤?

其实是三个步骤:

正常情况下,数据少,操作的线程少,问迹丛题倒是不大,想一想要是这样的情况下,操作次数大,对齐操作的线程多,有些线程从中间切入进来了,在运算之后还没写回内存就另外一个线程切入进来同时对于之前的数据进行++ 再写回内存, 啥效果,多次++ 操作之后结果确实一次加加操作后的结果。 这样的操作 (术语叫做函数的重入) 我觉得其实就是重入到了汇编指令中间了,还没将上一次运算的结果写回内存就重新对这个内存读取再运算写姿答樱入,结果肯定和正常的逻辑后的结果不一样呀

来一幅图片解释一下

咋办? 其实问题很清楚,我们只需要处理的是多条汇编指令不能让它中间入其他的线程运算. (要想自己在执行汇编指令的时候别人不插入进来) 将多条汇编指令绑定成为一条指令不就OK了嘛。

也就是原子操作!!!

不会原子操作?操作系统给咱提供了线程的 绑定方式工具呀:mutex 互斥锁(互斥量), 自旋锁(spinlock), 读写锁(readers-writer lock) 他们也称作悲观锁. 作用都是一个样,将多个汇编指令锁成为一条原子操作 (此处的汇编指令也相当于如下的临界资源)

悲观锁:锁如其名,每次都悲观地认为其他线程也会来修改数据,进行写入操作,所以会在取数据前先加锁保护,当其他线程想要访问数据时,被阻塞挂起

乐观锁:每次取数据的时候,总是乐观地认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。

互斥锁

最为常见使用地锁就是互斥锁, 也称互斥量. mutex

特征,当其他线程持有互斥锁对临界资源做写入操作地时候,当前线程只能挂起等待,让出CPU,存在线程间切换工作

解释一下存在线程间切换工作 : 当线程试图去获取锁对临界资源做写入操作时候,如果锁被别的线程正在持有,该线程会保存上下文直接挂起,让出CPU,等到锁被释放出来再进行线程间切换,从新持有CPU执行写入操作

互斥锁需要进行线程间切换,相比自旋锁而言性能会差上许多,因为自旋锁不会让出CPU, 也就不需要进行线程间切换的步骤,具体原理下一点详述

加互斥量(互斥锁)确实可以达到要求,但是会发现运行时间非常的长,因为线程间不断地切换也需要时间, 线程间切换的代价比较大.

相关视频推荐

你绕不开的组件—锁,4个方面手撕锁的多种实现

“惊群”原理、锁的设计方案及绕不开的“死锁”问题

学习视频教程-腾讯课堂

需要C/C++ Linux服务器架构师学习资料加qun获取(资料包括

C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg

等),免费分享

自旋锁

spinlock.自旋锁.

对比互斥量(互斥锁)而言,获取自旋锁不需要进行线程间切换,如果自旋锁正在被别的线程占用,该线程也不会放弃CPU进行挂起休眠,而是恰如其名的在哪里不断地循环地查看自旋锁保持者(持有者)是否将自旋锁资源释放出来… (自旋地原来就是如此)

口语解释自旋:持有自旋锁的线程不释放自旋锁,那也没有关系呀,我就在这里不断地一遍又一遍地查询自旋锁是否释放出来,一旦释放出来我立马就可以直接使用 (因为我并没有挂起等待,不需要像互斥锁还需要进行线程间切换,重新获取CPU,保存恢复上下文等等操作)

哪正是因为上述这些特点,线程尝试获取自旋锁,获取不到不会采取休眠挂起地方式,而是原地自旋(一遍又一遍查询自旋锁是否可以获取)效率是远高于互斥锁了. 那我们是不是所有情况都使用自旋锁就行了呢,互斥锁就可以放弃使用了吗????

解释自旋锁地弊端:如果每一个线程都仅仅只是需要短时间获取这个锁,那我自旋占据CPU等待是没啥问题地。要是线程需要长时间地使用占据(锁)。。。 会造成过多地无端占据CPU资源,俗称站着茅坑不拉屎… 但是要是仅仅是短时间地自旋,平衡CPU利用率 + 程序运行效率 (自旋锁确实是在有些时候更加合适)

自旋锁需要场景:内核可抢占或者P(多处理器)情况下才真正需求 (避免死锁陷入死循环,疯狂地自旋,比如递归获取自旋锁. 你获取了还要获取,但是又没法释放)

自旋锁的使用函数其实和互斥锁几乎是一摸一样地,仅仅只是需要将所有的mutex换成spin即可

仅仅只是在init存在些许不同

何为惊群,池塘一堆, 我瞄准一条插过去,但是好似所有的都像是觉着自己正在一样的四处逃窜。 这个就是惊群的生活一点的理解

惊群现象其实一点也不少,比如说 accept pthread_cond_broadcast 还有多个线程共享epoll监视一个listenfd 然后此刻 listenfd 说来 SYN了,放在了SYN队列中,然后完成了三次握手放在了 accept队列中了, 现在问题是这个connect我应该交付给哪一个线程处理呢.

多个epoll监视准备工作的线程 就是这群 (),然后connet就是鱼叉,这一叉下去肯定是所有的 epoll线程都会被惊醒 (多线程共享listenfd引发的epoll惊群)

同样如果将上述的多个线程换成多个进程共享监视 同一个 listenfd 就是(多进程的epoll惊群现象)

咱再画一个草图再来理解一下这个惊群:

如果是多进程道理是一样滴,仅仅只是将所有的线程换成进程就OK了

终是来到了今天的正题了: epoll惊群问题地解决上面了…

首先 先说说accept的惊群问题,没想到吧accept 平时大家写它的多线程地时候,多个线程同时accept同一个listensock地时候也是会存在惊群问题地,但是accept地惊群问题已经被Linux内核处理了: 当有新的连接进入到accept队列的时候,内核唤醒且仅唤醒一个进程来处理

但是对于epoll的惊群问题,内核却没有直接进行处理。哪既然内核没有直接帮我们处理,我们应该如何针对这种现象做出一定的措施呢?

惊群效应带来的弊端: 惊群现象会造成epoll的伪唤醒,本来epoll是阻塞挂起等待着地,这个时候因为挂起等待是不会占用CPU地。。。 但是一旦唤醒就会占用CPU去处理发生地IO事件, 但是其实是一个伪唤醒,这个就是对于线程或者进程的无效调度。然而进程或者线程地调取是需要花费代价地,需要上下文切换。需要进行进程(线程)间的不断切换… 本来多核CPU是用来支持高并发地,但是现在却被用来无效地唤醒,对于多核CPU简直就是一种浪费 (浪费系统资源) 还会影响系统的性能.

解决方式(一般是两种)

Nginx的解决方式:

加锁:惊群问题发生的前提是多个进程(线程)监听同一个套接字(listensock)上的事件,所以我们只让一个进程(线程)去处理监听套接字就可以了。

画两张图来理解一下:

上述还没有进行一个每一个进程都对应一个listensock 而是多线程共享一个listensock 运行结果如下

所有的线程同时被唤醒了,但是实际上会处理连接的仅仅只是一个线程,

咱仅仅只是将主线程做如上这样一个简单的修改,每一个线程对应一个listensock;每一个线程一个独有的监视窗口,将问题抛给内核去处理,让内核去负载均衡 : 结果如下

仅仅唤醒一个线程来进行处理连接,解决了惊群问题

本文通过介绍两种锁入手,以及为什么需要锁,锁本质就是为了保护,持有锁你就有权力有能力操作写入一定的临界保护资源,没有锁你就不行需要等待,本质其实是将多条汇编指令绑定成原子操作

然后介绍了惊群现象,通过一个巧妙地例子,扔一颗石子,只是瞄准一条鱼扔过去了,但是整池鱼都被惊醒了,

对应我们地实际问题就是, 多个线程或者进程共同监视同一个listensock。。。。然后IO连接事件到来地时候本来仅仅只是需要一个线程醒过来处理即可,但是却会使得所有地线程(进程)全部醒过来,造成不必要地进程线程间切换,多核CPU被浪费喔,系统资源被浪费

处理方式 一。 Nginx 源码加互斥锁处理。。 二。设置SO_REUSEPORT, 使得多个进程线程可以同时连接同一个port , 为每一个进程线程搞一个listensock… 将问题抛给内核去处理,让他去负载均衡地仅仅将IO连接事件分配给一个进程或线程

linux下信号量和互斥锁的区别

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都族渗团在semtake的时候,就阻塞在哪里)。

而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完喊蔽了,在解锁。

有的时候锁和信号量会同时使用的兆橘。我记得以前做的一个项目就是既有semtake,又有lock。

信号量与互斥锁之间的区册迹别:

1.

互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无数肆序的。

同步:是指在互州毕并斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2.

互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

3.

互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

关于linux互斥锁封装的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。


数据运维技术 » Linux互斥锁封装:保障多线程同步安全 (linux互斥锁封装)