锁与原子操作
创始人
2024-05-24 09:36:54
0

锁与原子操作

以自增操作为例子:

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,还需线程私用空间

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...