学习笔记_原子操作CAS和锁实现

学习笔记_原子操作CAS和锁实现

加锁是对一块临界资源加锁,加锁是对临界资源的操作, 所有的占用(临界资源在工作)

// idx++, 自增操作的汇编实现其实是3条指令
Mov [idx], %eax
Inc %eax
Mov %eax,[idx]

互斥锁

pthread_mutex_t, 会进行线程的切换, 切换去执行其他任务, 执行完在来判断临界找资源是否可用

使用场景:临界资源操作复杂(比线程切换复杂), 或有发生系统调用, 优先选择mutex, 如文件读写

pthread_mutex_t mutex;
pthread_mutex_init(&mutex);
pthread_mutex_lock(&mutex);
idx++;
pthread_mutex_unlock(&mutex);

自旋锁

pthread_spinlock_t, 死等, 自旋不断等待临界资源被释放

使用场景:临界资源操作简单(比线程切换复杂), 或没有发生系统调用, 优先选择spinlock, 如++

pthread_spinlock_t mutex;
pthread_spin_init(&mutex);
pthread_spin_lock(&mutex);
idx++;
pthread_spin_unlock(&mutex);

原子操作

CPU指令集是否支持原子操作, 原子操作的实现原理

// old=第零个参数, value等于第一个参数, add等于第二个参数
// xaddl表示将idx++的三条指令和为1条
int inc(int *value, int add) {
    int old;
    __asm__ volatile (
        "lock; xaddl %2, %1;"
        : "=a" (old)
        : "m" (*value), "a" (add)
        : "cc", "memory"
    );
    return old;
}

CAS(Compare and Swap)

CAS只是原子操作的一种, if(a == b) {a = c};先对比, 后赋值, CPU的指令集cmpschg(a, b,c);

线程私有空间

进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有。在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决。在线程内部,私有数据可以被线程的各个接口访问,但对其他线程屏蔽。

线程私有数据采用了一键多值技术,及一个key对应多个值。访问数据都是通过键值来访的。

pthread_key_t key; // 创建线程的私有空间, 线程私有的堆栈空间

pthread_key_create(&key, NULL);// 创建一个键

// 该接口用于删除一个键,功能仅仅是将该key在结构体数组pthread_keys对应的元素设置为“un_use”,与改key相关联的线程数据是不会被释放的,因此线程私有数据的释放必须在键删除之前。

pthread_key_delete(key);

pthread_setspecific(key, arg);// 为指定键值设置线程私有数据

pthread_getspecific(key);//从指定键读取线程的私有数据

try-catch-finally的实现

与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。

为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。

  1. setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
  2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)

通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。

try为"0 == setjmp(env)", catch为"other_value == setjmp(env)";

// 通过链表来处理try-catch嵌套的问题
struct ExceptionFrame {
    jmp_buf env;
    int count;

    struct ExceptionFrame *next;
};

#define Try count = setjmp(env); if(count == 0)
#define Catch(type) else if(count == type)
#define Throw(type) longjmp(env, type)
#define Finally
  1. try-catch嵌套, 通过链表解决
  2. try-catch的栈保存在哪里, 通过线程的私有空间解决
  3. 线程安全的问题, 线程a, setjmp, 线程b, longjmp, 通过线程的私有空间

CPU亲缘性, CPU粘合

为了更大限度地利用CPU, 保证CPU优先执行该进程/线程, 进程和线程都可以粘合

int num = sysconf(_SC_NPROCESSORS_CONF);

加锁的粒度

在加锁的时候需要考虑粒度的问题, 只针对需要加锁的资源加锁, 其他的坚决不加.

其他

  1. 为什么会进入稳态?编译的时候设置不优化-o0,count++会出现多线程汇编指令的多线程交叉运行.
  2. 调用longjmp的函数是没有返回的, 即函数没有出栈
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值