首页 > 其他分享 >多线程下写全局变量时,可借助sleep(0)让出cpu

多线程下写全局变量时,可借助sleep(0)让出cpu

时间:2024-04-10 22:25:18浏览次数:27  
标签:count cnt ++ int 线程 sleep 下写 多线程

目录

近期在重读APUE,对unix下多线程有了新的理解
用一个小demo来说明多线程下写全局变量时,让出cpu(使线程挂起)的重要性

一个demo(对全局变量++)-->反汇编阅读cpu指令

下面这个demo对g_cnt这个全局变量进行++操作

int g_cnt = 1;

int main()
{
    g_cnt++;

    return 0;
}

执行gcc -S main.c可以查看其反汇编,挑几行关键的看看:

movl	g_cnt(%rip), %eax
addl	$1, %eax
movl	%eax, g_cnt(%rip)

分析一下,上面有3个步骤:
[1]load,将g_cnt的值写入寄存器eax
[2]对寄存器eax进行+1操作
[3]写回,将eax寄存器的值写回g_cnt

多个线程都去对全局变量++

线程不挂起

先看这个demo,共创建了10个线程,每个线程对g_count进行++,共10W次,期望g_count最终值为10W,
注意下面的sleep(0)在每个线程的回调函数中被注释掉了

#define THREAD_NUM 10

int g_count = 0;

void *func(void *arg)
{
    int *cnt = (int *)arg;

    // 每个线程加 1W 次
    for (int i = 0; i < 10000; i++) {
        (*cnt)++;

        // sleep(0); // 使线程挂起(导致切换),让出cpu
    }
}

int main()
{
    int i;
    pthread_t threadId[THREAD_NUM];

    for (i = 0; i < THREAD_NUM; i++) {
        pthread_create(&threadId[i], NULL, func, (void *)&g_count);
    }

    for (i = 0; i < 15; i++) {
        printf("---> g_count=%d\n", g_count);
        sleep(1);
    }

    return 0;
}

看看效果,g_count最终只被加到6W多(每次执行结果都不一样,但是都距离10w差距很远),往下再看一节再分析原因
image

sleep(0)使线程挂起,让出cpu

还是这个demo,但是把sleep(0)放开注释

#define THREAD_NUM 10

int g_count = 0;

void *func(void *arg)
{
    int *cnt = (int *)arg;

    // 每个线程加 1W 次
    for (int i = 0; i < 10000; i++) {
        (*cnt)++;

        sleep(0); // 使线程挂起(导致切换),让出cpu
    }
}

int main()
{
    int i;
    pthread_t threadId[THREAD_NUM];

    for (i = 0; i < THREAD_NUM; i++) {
        pthread_create(&threadId[i], NULL, func, (void *)&g_count);
    }

    for (i = 0; i < 15; i++) {
        printf("---> g_count=%d\n", g_count);
        sleep(1);
    }

    return 0;
}

看看效果,有了sleep(0)已经很接近10w了
image

总结一下

线程不挂起小节中,10个线程可能只有部分线程抢到了cpu,导致其他线程根本没机会被调度
后续增加了sleep(0)强行让线程挂起,让出cpu,每个线程都得到了机会被调度,进而对全局变量++

为啥不到10W?

回忆以下前面的反汇编看到的指令:

movl	g_cnt(%rip), %eax
addl	$1, %eax
movl	%eax, g_cnt(%rip)

共3条指令,但是他们不具备原子性,不能保证每个线程都是乖乖的执行完这3条,再让其他线程再去执行,看看下图:

  • 理想情况
    image
  • 其他情况
    这里的eax寄存器我的理解是多核(多线程)不是共享的,假设g_cnt初始化为0;线程1load g_cnt到线程1的eax寄存器中,此时线程2load g_cnt到线程2的eax寄存器中,执行++后,进行写回,g_cnt值加到1,;此时线程1又对线程1的eax寄存器的值进行++,再写回到g_cnt还是1;两个线程都做了++操作,但是值只增加了1。
    这样g_cnt可能被++的次数就变少了,因此没有达到10W,解决的方法很容易想到:加锁
    image

加锁版本

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

#define THREAD_NUM 10

int g_count = 0;
pthread_mutex_t mutex;

void *func(void *arg)
{
    int *cnt = (int *)arg;

    // 每个线程加 1W 次
    for (int i = 0; i < 10000; i++) {

        pthread_mutex_lock(&mutex);

        (*cnt)++;

        pthread_mutex_unlock(&mutex);

        sleep(0);// 使线程挂起(导致切换),让出cpu
    }
}

int main()
{
    int i;
    pthread_t threadId[THREAD_NUM];

    pthread_mutex_init(&mutex, NULL);

    for (i = 0; i < THREAD_NUM; i++) {
        pthread_create(&threadId[i], NULL, func, (void *)&g_count);
    }

    for (i = 0; i < 15; i++) {
        printf("---> g_count=%d\n", g_count);
        sleep(1);
    }

    return 0;
}

这样就可以加到10w了~
image

标签:count,cnt,++,int,线程,sleep,下写,多线程
From: https://www.cnblogs.com/kongweisi/p/18127476

相关文章

  • 多线程(进阶篇&小白易懂版)
    文章目录多线程为什么要有多线程多线程案例线程通讯分传主线程通讯主传分关闭线程线程锁多线程概念:多线程就是多个线程同时工作的过程,我们可以将线程看作是程序的执行路径,每个线程都定义了一个独特的控制流,用来完成特定的任务。如果您的应用程序涉及到复杂且耗时的......
  • 用本小组项目中实际的例子来重现如下问题: 1、代码覆盖率对于“应该写但是没有写的代
    例子1-代码覆盖率无法检测资源管理问题:假设在移动充电桩应用中有一个负责与服务器通信的模块,它从服务器下载充电站的实时状态信息。开发者编写了一段代码来连接服务器、发送请求并接收响应数据,但是在处理完响应后,忘记关闭网络连接或释放相关资源:JavapublicclassChargingSta......
  • 多线程 p2
    多线程概念​Java中的多线程概念指的是在java程序中同时执行多个进程的技术。Java提供了内置的多线程支持。Java的多线程编程可以用于实现并行计算、提升程序的响应性、处理异步任务等场景JAVA线程实现/创建方式1.继承Thread类​//继承Thread类,重写run()方法,调用start......
  • 《架构风清扬-Java面试系列第19讲》解释一下Java中的“volatile”在多线程环境中的作
    适用范围:这道题适应范围挺宽的,各个年限都可以用参考答案:主要用于确保变量在多个线程之间的可见性和有序性。可见性:当一个线程修改了被volatile修饰的变量,其他线程能够立即看到修改后的值。这确保了变量在多个线程之间的可见性。有序性:volatile关键字能够防止指令重排序......
  • 多线程
    多线程一、多线程理论[1]什么线程在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程线程顾名思义,就是一条流水线工作的过程一条流水线必须属于一个车间,一个车间的工作过程是一个进程车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线......
  • Java基础知识-面向对象编程(OOP)-Java集合框架-多线程和并发-Spring框架
    Java基础知识:Java的四种基本数据类型是:byte、short、int、long(整数类型)、float、double(浮点类型)、char(字符类型)、boolean(布尔类型)。它们之间的区别主要在于占用的内存大小和表示范围不同。Java中的String是不可变的意味着一旦String对象被创建,它的值就不能被修改。这意味着St......
  • 简述多线程中的锁与sleep
    面试中经常被问到,在多线程/加锁环境下使用sleep可能出现的问题,首先总结一下这些问题基本都出自sleep不会释放锁这一点(与wait()截然相反)。1sleep可能会引发的问题线程持有锁时休眠:当一个线程在持有锁的情况下调用sleep()时,它会在睡眠时仍保持锁的状态,此时其他线程将无法访......
  • 多线程面试要点
    一、线程的基础知识1、线程和进程的区别一个线程就是一个指令流,将指令流中的一条条指令以一定顺序交给CPU执行一个进程之内可以分为一到多个线程。二者对比进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务。不同的进程使用不同的内存空间,在当前进程......
  • JavaEE初阶Day 5:多线程(3)
    目录Day5:多线程(3)1.join2.再谈sleep3.线程的状态4.线程安全问题Day5:多线程(3)多线程在整个编程中都是非常核心非常重要的话题多核CPU客观的主流的需求多线程这里还是有一定难度/不少注意事项的回顾Thread创建的写法继承Thread,重写run实现Runnable,重写run......
  • 30_多线程
    多线程多线程简介​ 线程,是一种允许一个正在运行的程序同时执行不止一个任务的机制。不同线程看起来是并行运行的;Linux操作系统对线程进行异步调度,不断中断它们的执行以给其它线程执行的机会。​ 线程与进程的区别:线程是进程中的一个独立并发执行的路径,进程退出时,线程也会退......