5. 多线程并发锁

news2024/11/27 12:33:21

本文介绍了多线程并发下为了避免临界资源被抢占而出现的错误,引入了锁和原子操作 来解决。

一、问题分析

创建10个线程,每个线程实现往总进程加1万个数。则总进程会达到10万

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

#define THREAD_COUNT    10

void *thread_callback(void* arg){
    int *pcount=(int *)arg;
    int i=0;
    while(i++ <10000){
        (*pcount)++;

        //usleep(1)是一个系统调用函数,它的作用是让当前进程暂停执行一微秒(即等待一微秒
        usleep(1);
    }
}

int main(){
    pthread_t threadid[THREAD_COUNT]={0};
    int i=0;
    int count=0;

    //创建10个线程
    for (i=0;i<THREAD_COUNT;i++){
        pthread_create(&threadid[i],NULL,thread_callback,&count);
    }

    for (i=0;i<100;i++){
        printf("count :%d\n",count);

        //sleep(n)是一个系统调用函数,它的作用是让当前进程暂停执行n秒钟(即等待n秒钟)
        sleep(1);
    }
}

但实现过程中,会发现最后达不到100万。这种现象的原因在于count是一个临界资源,即多个线程共用一个变量。我们分析一下count++的汇编指令

mov count,eax;
inc eax;
mov eax,count;

实际中却可能出现如下异常情况
在这里插入图片描述
解决办法是加锁,即打包锁住count++的三个汇编指令,不可分开,其他线程不可抢占。

二、 锁和原子操作

在这里插入图片描述
把count的三条汇编指令变成一条指令,就不需要加锁。这种称为原子操作。

原子操作是指在并发环境下对共享资源进行访问或修改时,能够保证该操作具有不可分割性和独占性的一种操作方式。通常情况下,原子操作是由硬件级别提供支持的,可以确保在执行过程中不会被中断或者其他线程干扰,从而避免了竞态条件等问题的发生。

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

#define THREAD_COUNT    10
pthread_mutex_t mutex;
pthread_spinlock_t spinlock;

//对value加上add
int inc(int *value,int add){
    int old;

    //__asm__是GCC的内嵌汇编语言指令
    __asm__ volatile(
        "lock;xaddl %2,%1;"// %2的值+%1的值,赋给%1;同时,在执行该指令期间,CPU会对总线进行锁定(lock),以确保整个操作是原子性地完成。
        : "=a" (old)
        : "m" (*value),"a"(add)
        : "cc","memory"
    );
    return old;
}

void *thread_callback(void* arg){
    int *pcount=(int *)arg;
    int i=0;
    while(i++ <10000){
#if 0
    (*pcount)++;
#elif 0
    pthread_mutex_lock(&mutex);
    (*pcount)++;
    pthread_mutex_unlock(&mutex);
#elif 0
    pthread_spin_lock(&spinlock);
    (*pcount)++;
    pthread_spin_unlock(&spinlock);
#else
    inc(pcount,1);
#endif

    usleep(1);
    }
}

int main(){
    pthread_t threadid[THREAD_COUNT]={0};

    pthread_mutex_init(&mutex,NULL);//初始化mutex
    pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED);//初始化spinlock,进程之间共享

    int i=0;
    int count=0;

    //创建10个线程
    for (i=0;i<THREAD_COUNT;i++){
        pthread_create(&threadid[i],NULL,thread_callback,&count);
    }

    for (i=0;i<100;i++){
        printf("count :%d\n",count);

        //sleep(n)是一个系统调用函数,它的作用是让当前进程暂停执行n秒钟(即等待n秒钟)
        sleep(1);
    }
}

三、相关知识点补充

1、创建进程

pthread_t是一个线程标识符,它可以唯一地标识一个线程。在多线程编程中,我们通常使用pthread_create()函数来创建一个新的线程,并且该函数会返回一个pthread_t类型的值,以便我们在后续代码中对这个新线程进行操作。

pthread_create()是一个函数,用于创建一个新的线程。其原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

该函数接受四个参数:

  • thread:指向pthread_t类型变量的指针,用于存储新线程的标识符。
  • attr:指向pthread_attr_t类型变量的指针,用于设置新线程的属性(如栈大小、优先级等)。如果为NULL,则使用默认属性。
  • start_routine:指向新线程要执行的函数的指针。该函数必须返回void*类型,并且只能有一个参数,即对应于该函数调用时传递给它的参数。
  • arg: 传递给start_routine函数的参数。

例如,下面是一个简单的例子,演示了如何使用pthread_create()来创建一个新线程:

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

void* thread_func(void *arg) {
    printf("New thread created\n");
}

int main() {
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    
    // Wait for the new thread to complete
    pthread_join(tid, NULL);
    
    return 0;
}

在上述代码中,我们定义了一个名为“thread_func”的函数作为新线程要执行的内容。在主函数中,我们调用了pthread_create()函数来创建一个新线程,并将该函数的返回值存储到变量tid中。然后,我们通过调用pthread_join()函数等待新线程执行完毕。在新线程内部,我们只是简单地打印一条消息以表明它已经被创建成功了。

2,条件编译指令

#if 0
    (*pcount)++;
#else 
    pthread_mutex_lock(&mutex);
    (*pcount)++;
    pthread_mutex_unlock(&mutex);
#endif

这是一个预处理器指令,用于在编译时控制代码的执行。如果 #if 0 部分是注释掉的代码或者不需要执行的代码,则可以使用 #if 0 来暂时禁用它们,以免浪费系统资源和时间。类似地,#elif 0 可以用来替代一部分被注释掉的代码或者不需要执行的代码,而 #else 则是当所有上面的条件都不满足时要执行的语句块。

使用条件编译指令的好处是可以在编译时根据预处理器定义的宏来控制代码的执行,而不需要手动注释或取消注释一大段代码。这种方法在调试和测试代码时非常有用,因为可以轻松地开启或关闭某些特定功能的实现,从而简化了代码的维护和调试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551294.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路径规划算法:基于头脑风暴算法的路径规划算法- 附代码

路径规划算法&#xff1a;基于头脑风暴的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于头脑风暴的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法头脑…

强化学习-初步认识

前言 强化学习这个概念是2017年Alpha Go战胜了当时世界排名第一的柯洁而被大众知道&#xff0c;后面随着强化学习在各大游戏比如王者荣耀中被应用&#xff0c;而被越来越多人熟知。王者荣耀AI团队&#xff0c;甚至在顶级期刊AAAI上发表过强化学习在王者荣耀中应用的论文。 什么…

BEVDet4D 论文学习

1. 解决了什么问题&#xff1f; 单帧数据包含的信息很有限&#xff0c;制约了目前基于视觉的多相机 3D 目标检测方法的性能&#xff0c;尤其是关于速度预测任务&#xff0c;要远落后于基于 LiDAR 和 radar 的方法。 2. 提出了什么方法&#xff1f; BEVDet4D 将 BEVDet 方法从…

C++ Vecter

C Vecter &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了C中vector的介绍以及相关的一些接口的使用 …

Prometheus+Grafana监控系统

一、简介 1、Prometheus简介 官网&#xff1a;https://prometheus.io 项目代码&#xff1a;https://github.com/prometheus Prometheus&#xff08;普罗米修斯&#xff09;是一个最初在SoundCloud上构建的监控系统。自2012年成为社区开源项目&#xff0c;拥有非常活跃的开发人员…

第二章 Electron自定义界面(最大化、最小化、关闭、图标等等)

一、介绍 &#x1f606; &#x1f601; &#x1f609; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需…

linux--systemd、systemctl

linux--systemd、systemctl 1 介绍1.1 发展sysvinitupstart主角 systemd 登场 1.2 简介 2 优点兼容性启动速度systemd 提供按需启动能力采用 linux 的 cgroups 跟踪和管理进程的生命周期启动挂载点和自动挂载的管理实现事务性依赖关系管理日志服务systemd journal 的优点如下&a…

GAMS建模技术案例01 求解简单的线性规划模型

目录 1.问题描述 2 GAMS代码要点 2.1 代码编写要点 2.2 案例源码 2.3 计算结果及报告解 1.问题描述 首先给出一个基本线性规划问题的计算案例 subject to: 2 GAMS代码要点 2.1 代码编写要点 使用 * 表示注释文本定义变量 Positive Variable 表示定义非负变量Negative V…

【经典论文】打通文本图像的里程碑--clip

Git&#xff5c;Paper&#xff5c;Colab&#xff5c; CLIP 论文逐段精读【论文精读】_哔哩哔哩_bilibili clip是openai团队在4亿对文本图像对上训练出来的。它的训练方法简单&#xff0c;但效果缺出奇的好。是打通图片文本的里程碑式的模型。 目录 一.模型结构​编辑 1.为…

“Shell“免交互

文章目录 一.免交互&#xff08;Here Document&#xff09;1.1Here Document 概述2.2Here Document 常规用法 二.Expect2.1Expect基本命令2.2Expect执行方式 一.免交互&#xff08;Here Document&#xff09; 1.1Here Document 概述 使用I/O重定向的方式将命今列表提供给交互式…

chatgpt赋能Python-pythonandor

Pythonandor&#xff1a;探索Python的异步编程方式 如果您是一个Python工程师&#xff0c;你可能已经听过Pythonandor。Pythonandor是一个Python异步框架&#xff0c;能够让你更高效地处理请求&#xff0c;并且提高应用程序的响应速度。 什么是Pythonandor? Pythonandor实际…

5。STM32裸机开发(4)

嵌入式软件开发学习过程记录&#xff0c;本部分结合本人的学习经验撰写&#xff0c;系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维&#xff0c;为RTOS做铺垫&#xff08;本部分基于库函数版实现&#xff09;&#xff0c;如有不足之处&#xff0c;敬请批评指正。 &…

一篇文章打好SQL基础,熟悉数据库的基础操作和方法,以及安装MySQL软件包和Python操作MySQL基础使用

1.SQL的概述 SQL的全称&#xff1a;Structured Query Language&#xff0c;结构化查询语言&#xff0c;用于访问和处理数据库的标准计算机语言。 SQL语言1974年有Boyce和Chamberlin提出的&#xff0c;并且首先在IBM公司研制的关系数据库系统SystemR上实现。 经过多年发展&am…

KVM(二)命令行新建虚拟机

目录 一、准备工作 二、新建虚拟机 2.1 文件准备 2.2 正式安装 2.3 时区设置 2.4 安装设置 2.5 设置root用户密码 2.6 vm2安装完成 三、进入虚拟机vm2 四、网络设置 五、参考链接 若还未部署KVM&#xff0c;请参考第一节&#xff1a; KVM&#xff08;一&#xff09;…

如何让你的 Jmeter+Ant 测试报告更具吸引力?

目录 引言 一、安装apache-Ant 二、Jmeter准备 3、生成测试报告 4、JMeter动态参数处理逻辑是什么&#xff1f; 5、JMeter是怎么做API自动化测试的&#xff1f; 结语 引言 想象一下&#xff0c;你辛苦搭建了一个复杂的网站&#xff0c;投入了大量的时间和精力进行开发和…

java 对接国标摄像头流程、代码整合 springboot SIP -GB2818

java 对接设备的代码资料较少&#xff0c;这里介绍GB2818的基本对接流程&#xff0c;有用自取&#x1f447; java负责SIP信令的注册交互&#xff0c;推流、拉流鉴权摄像头负责推流、流媒体负责拉流、转码 wvp-GB28181-pro项目 ,如果java对接各种摄像头&#xff0c;这个项目很&a…

Java流程控制(二)

⭐ 循环结构⭐ 嵌套循环⭐ break 语句和 continue 语句⭐ 方法⭐ 方法的重载(overload)⭐ 递归结构 ⭐ 循环结构 循环结构分两大类&#xff0c;一类是当型&#xff0c;一类是直到型。 &#x1f41f; 当型&#xff1a; 当布尔表达式条件为 true 时&#xff0c;反复执行某语句&a…

【eNSP】win11解决virtualbox5.2.44无法安装、不兼容的问题

问题描述&#xff1a; 本人大三学生一枚&#xff0c;这学期上计算机网络&#xff0c;老师要求安装华为eNSP软件&#xff0c;安装环节一切顺利&#xff0c;直到安装到依赖组件中VirtualBox-5.2.44时&#xff0c;发生了问题&#xff0c;Windows提示此应用无法在此设备上运行&…

AI工具第三期:本周超16款国内精选AI工具分享!

1. 未来百科 未来百科&#xff0c;是一个知名的AI产品导航网站——为发现全球优质AI工具而生。目前已聚集全球2500优质AI工具产品&#xff0c;旨在帮助用户发现全球最好的AI工具&#xff0c;同时为研发AI垂直应用的创业公司提供展示窗口&#xff0c;迎接未来的AI时代。未来百科…

RocketMq源码分析(七)--消息发送流程

文章目录 一、消息发送入口二、消息发送流程1、消息验证1&#xff09;消息主题验证2&#xff09;消息内容验证 2、查找路由3、消息发送1&#xff09;选择消息队列2&#xff09;消息发送-内核实现sendKernelImpl方法参数获取brokerAddr添加消息全局唯一id设置实例id设置系统标记…