当前位置: 首页 > news >正文

锁与原子操作

锁与原子操作

以自增操作为例子:

void *func(void *arg) {int *pcount = (int *)arg;int i = 0;//while (i ++ < 100000) {(*pcount) ++;    // 并不会到达100000usleep(1);}
}int main(){int i = 0;for (i = 0;i < THREAD_COUNT;i ++) {pthread_create(&thid[i], NULL, func, &count);}for (i = 0;i < 100;i ++) {printf("count --> %d\n", count);sleep(1);}
}

如果有两个线程对i=20进行自增:idx++

正常是这样:

20230205222319

从内存加载到寄存器 =》 寄存器自增 =》 从寄存器加载到内存

但可能是:

20230205222642

在线程1切换2时,没问题,在线程2切换线程1时,线程1栈中保存的寄存器的值INC是20,写入内存就是21,而不是22.

如果开启O3优化,编译器会将自增转换为原子操作(后面再将)

那我们在编码层怎么做:

  • 加锁(对临界资源是否需要加锁,看它在汇编层面是否是原子操作 )
  • 直接把操作变成原子操作

互斥锁、自旋锁、原子操作

  1. 锁,就是对临界资源加锁,这里加一把互斥锁
void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i ++ < 100000) {pthread_mutex_lock(&mutex);  (*pcount) ++;pthread_mutex_unlock(&mutex);usleep(1);}
}

但其实,这里更应该用自旋锁

  1. 自旋锁:和互斥锁使用一样的,互斥锁在哪用,自旋锁就在哪里用(不让出cpu,一直等着锁被释放)

使用场景:

  • 临界资源复杂的,有系统调用的操作用互斥锁,简单的用自旋(因为等的时间短,消耗的资源还少于线程切换的资源),
  • 有系统调用的就别用自旋了,文件的读写,只允许一个线程访问的,用互斥锁,自增操作,队列读取可以用自旋锁
  • 操作简单,且cpu提供了指令集的,用原子操作
  1. 原子操作

把三条指令(读、自增、写)变成一条:xaddl

原子操作需要cpu指令集的支持,比如这里的xaddl

int inc(int *value, int add) {int old;__asm__ volatile (// xaddl 第2个参数加第1个参数并把值存储到第一个参数;lock,锁cpu操作内存的总线// 锁总线、锁缓存的平时也用不到,这里不赘述了"lock; xaddl %2, %1;"   : "=a" (old)    // old:第0个参数: "m" (*value), "a" (add)    // value第一个参数,add是第二个参数: "cc", "memory");return old;
}void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i ++ < 100000) {inc(pcount, 1);   // 原子操作usleep(1);}
}

而cas是原子操作的一种,也就是cpu指令集中的一个指令:compare and swap,先有比较再有赋值

if (a==b){   // comparea=c;      // swap
}

这个就是cmpxchg(a,b,c),用在单例模式中:

if (instance == null){instance = malloc(sizeof(object));
}

原子操作记住常用的就行 自增,自减,加减乘除,cas

cpu亲缘性:
在Linux内核中,都是通过task_struct进行调度的,为了避免一个task_struct被切换到其他核(减少系统调用),可以使用系统函数sched_setaffinity() 将一个或多个task_struct绑定到特定的核上

tip1:如果fork指定数量的子进程:

比如说我要创建6个子进程,如果fork()3次,那就是8个了(2,4,8),可以通过以下方式创建:

    int i = 0;int num = sysconf(_SC_NPROCESSORS_CONF);  // 获取cpu核心数量pid_t pid = 0;for (i = 0;i < num/2;i ++) {pid = fork();if (pid <= (pid_t)0) {    // 如果是子线程(=0),就退出break;}}

tip2:以下内存我没深入理解,放在这里以后补充

线程私有空间:pthrerad_key

线程是进程的一个实体(),是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

线程所独享的资源有:程序计数器、寄存器、栈、状态字,共享堆内存

为什么线程要有私有数据:
比如不同线程监听不同的端口,端口的数据,就属于线程的私有空间,不应该被其他线程读取,而线程的私有数据是通过一个Key结构体来实现的

setjmp/longjmp:函数间的跳转,实现try-catch的核心,要实现try-catch,还需线程私用空间

http://www.lryc.cn/news/481.html

相关文章:

  • Prometheus Pushgetway讲解与实战操作
  • 常见字符串函数的使用,你确定不进来看看吗?
  • Elasticsearch:在搜索中使用衰减函数(Gauss)
  • 微信小程序 Springboot英语在线学习助手系统 uniapp
  • LeetCode算法题解——双指针2
  • 线性杂双功能peg化试剂——HS-PEG-COOH,Thiol-PEG-Acid
  • Linux第三讲
  • SpringBoot07:SpringSecurity
  • C++ 浅谈之 STL Vector
  • 【个人作品】非侵入式智能开关
  • 数据存储技术复习(三)未完
  • ThinkPHP数据库迁移工具
  • 代理模式(Proxy Pattern)
  • Elasticesearch内存详解
  • SpringCloud之断路器聚合监控
  • 凭借这份《2022测试八股文》候选者逆袭面试官,offer拿到手软
  • 【i2c协议介绍】
  • 167. 两数之和 II - 输入有序数组
  • 编译与链接------《程序员的自我修养》
  • 5分钟搞懂 强缓存与协商缓存
  • Ts笔记第一天
  • Android 12 Activity启动流程
  • VCS®/VCSi™User Guide
  • MongoDB简介及SpringBoot整合
  • 读书思考:步步惊心的《技术陷阱》
  • 求你了,不要再在对外接口中使用枚举类型了!
  • Java开发学习(四十六)----MyBatisPlus新增语句之id生成策略控制及其简化配置
  • 章鱼哥听歌
  • 软件测试电商项目实战(写进简历没问题)
  • 算法导论—分治法思想、动态规划思想、贪心思想