并发编程中用到的几种常见锁

news/2025/2/16 6:18:57/

没有加锁而造成的数据竞争

任务:使用10个线程,同时对一个count加100000;最后我们期望的结果是100000;

实验代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>#define THREAD_COUNT	10//线程回调函数
void *thread_callback(void *args)
{int *pcount = (int *)args;int i = 0;//依次增加100000while(i ++ < 100000){(*pcount)++; //无锁	usleep(1); //睡眠1微秒}}int main()
{clock_t start, finish;void *thread_result;pthread_t threadid[THREAD_COUNT] = {0};start = clock();int i = 0;int count = 0;for(i = 0; i < THREAD_COUNT; ++i){//创建线程pthread_create(&threadid[i], NULL, thread_callback, &count);}for(i = 0; i < THREAD_COUNT; ++i) {pthread_join(threadid[i], &thread_result);}finish = clock();printf("count: %d\n", count);printf("NoLock : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);return 0;
}

实验结果

在这里插入图片描述

观察发现,count并没有加到1000000,原因如下:

(*pcount)++可以分解为3条汇编指令

mov [count], eax;
inc eax;
mov eax, [count];

在并发环境下,会出现如下的执行顺序

在这里插入图片描述

如:count = 50, 线程1先执行 mov [count], eax;即eax = 50

然后转到线程2执行三条汇编指令,执行后count为51;

此时再转回去线程1执行剩余两条语句,由于前面eax = 50,经过inc eax之后eax变为51,再经过mov eax, [count],count 变为51,发现两次执行(*pcount)++之后 *pcount只自增了1。

*解决办法:在进行 (pcount)++时进行加锁

互斥锁(mutex)

实现代码如下

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>#define THREAD_COUNT	10pthread_mutex_t mutex;
//线程回调函数
void *thread_callback(void *args)
{int *pcount = (int *)args;int i = 0;//依次增加100000while(i ++ < 100000){pthread_mutex_lock(&mutex); //加互斥锁(*pcount)++;pthread_mutex_unlock(&mutex); //解锁usleep(1); //睡眠1微秒}}int main()
{clock_t start, finish;void *thread_result;pthread_t threadid[THREAD_COUNT] = {0};start = clock();pthread_mutex_init(&mutex, NULL);int i = 0;int count = 0;for(i = 0; i < THREAD_COUNT; ++i){//创建线程pthread_create(&threadid[i], NULL, thread_callback, &count);}for(i = 0; i < THREAD_COUNT; ++i) {pthread_join(threadid[i], &thread_result);}finish = clock();printf("count: %d\n", count);printf("mutex : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);pthread_mutex_destroy(&mutex);return 0;
}

实验结果

在这里插入图片描述

可以看出,count最终加到了1000000,解决了数据竞争的问题,但是互斥锁会引起线程切换,适合于锁的内容比较多的场景使用,比如线程安全的rbtree添加节点。

自旋锁(spinlock)

自旋锁不会引起线程切换,不会让出CPU,一直在原地空转。适合于锁的内容较少的情况下使用。

代码如下

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>#define THREAD_COUNT	10pthread_spinlock_t spinlock;
//线程回调函数
void *thread_callback(void *args)
{int *pcount = (int *)args;int i = 0;//依次增加100000while(i ++ < 100000){pthread_spin_lock(&spinlock);(*pcount)++;pthread_spin_unlock(&spinlock);//usleep(1); //睡眠1微秒}}int main()
{clock_t start, finish;void *thread_result;pthread_t threadid[THREAD_COUNT] = {0};start = clock();pthread_spin_init(&mutex, NULL);int i = 0;int count = 0;for(i = 0; i < THREAD_COUNT; ++i){//创建线程pthread_create(&threadid[i], NULL, thread_callback, &count);}for(i = 0; i < THREAD_COUNT; ++i) {pthread_join(threadid[i], &thread_result);}finish = clock();printf("count: %d\n", count);printf("mutex : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);pthread_spin_destroy(&mutex);return 0;
}

实验结果

在这里插入图片描述

可以看出也能解决数据竞争问题,但是时间效率低。

原子操作

原子操作就是通过汇编实现单条CPU指令实现(*pcount++)操作

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>#define THREAD_COUNT	10//将三条汇编合成一个原子操作
int inc(int *value, int add)
{int old;__asm__ volatile("lock; xaddl %2, %1;" //%1 = %2 + %1: "=a" (old): "m" (*value), "a"(add) //%1为 *value, %2为add: "cc", "memory");return old;}
//线程回调函数
void *thread_callback(void *args)
{int *pcount = (int *)args;int i = 0;//依次增加100000while(i ++ < 100000){//封装成原子操作inc(pcount, 1); }}int main()
{clock_t start, finish;void *thread_result;pthread_t threadid[THREAD_COUNT] = {0};start = clock();int i = 0;int count = 0;for(i = 0; i < THREAD_COUNT; ++i){//创建线程pthread_create(&threadid[i], NULL, thread_callback, &count);}for(i = 0; i < THREAD_COUNT; ++i) {pthread_join(threadid[i], &thread_result);}finish = clock();printf("count: %d\n", count);printf("atomic : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);return 0;
}

实验结果

在这里插入图片描述

可以看出原子操作的时间开销相比于互斥锁以及自旋锁是最低的。


http://www.ppmy.cn/news/8070.html

相关文章

有哪些简单、免费、适合中小型企业的 CRM 软件?

有哪些简单、免费、适合中小型企业的 CRM 软件&#xff1f; 为了更好的管理客户&#xff0c;和客户建立持续的良好关系&#xff0c;很多企业开始采用CRM软件。 但是免费且好用的CRM软件并不多见&#xff0c;因此选择一款适合中小型企业的CRM软件需要注意以下方面。 1. 知己&…

字符设备驱动(三)-----并发控制

1.上下文和并发场合 执行流&#xff1a;有开始有结束总体顺序执行的一段代码 又称上下文 应用编程&#xff1a;任务上下文 内核编程&#xff1a; 任务上下文&#xff1a;五状态 可阻塞 a. 应用进程或线程运行在用户空间 b. 应用进程或线程运行在内核空间&#xff08;通过调用…

机器视觉2D/3D标注工具汇总

标注工具是处理原始数据的第一关,无论是检测任务、分割任务还是3D感知、点云等,都需要制作真值来监督网络学习。企业级的标注方案一般通过内部的自研工具或专业标注团队完成,而对于个人或小的团队来说,一款开源好用的标注工具则至关重要。 1. 检测和分割 1) Labelme 项目…

JAVA注解处理API实战

简介 ​ 插件化注解处理(Pluggable Annotation Processing)API JSR 269提供一套标准API来处理Annotations( JSR 175),实际上JSR 269不仅仅用来处理Annotation&#xff0c;它建立了Java 语言本身的一个模型,它把method、package、constructor、type、variable、enum、annotatio…

ArcGIS基础实验操作100例--实验21按区域修改栅格值

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验21 按区域修改栅格值 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

公司jmeter分享

一、数据库压测组件功能说明 1.JDBC Connection Configuration:jdbc连接配置(一个测试计划可以有多个 JDBC Connection) 2.Variable Name for created pool: 创建池的变量名 连接绑定的变量名,JMeter可以使用多个连接,每个连接绑定到不同的变量;通过引用不同的绑定变量…

【Web安全】Ysoserial 简单利用

Ysoserial 简单利用1. Java 反序列化特征2. Ysoserial 流量特征3. Ysoserial 攻击流程3.1 找到序列化接口3.2 漏洞利用3.2.1 常用命令3.2.2 使用案例4. Ysoserial 攻击原理问题参考1. Java 反序列化特征 在日志中&#xff0c;特征通常表现为 请求格式 Json、xml、soap、二进制…

js实现复制粘贴剪切功能

文章目录js实现复制粘贴功能方式一&#xff1a;原生方式实现复制粘贴剪切&#xff08;不推荐&#xff09;方式二&#xff1a;浏览器自带clipboard API实现复制粘贴&#xff08;推荐&#xff09;简介特点clipboard对象及相关APIClipboard.readText()Clipboard.read()Clipboard.w…