学习笔记_原子操作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数据类型。
- setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
- 以后调用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
- try-catch嵌套, 通过链表解决
- try-catch的栈保存在哪里, 通过线程的私有空间解决
- 线程安全的问题, 线程a, setjmp, 线程b, longjmp, 通过线程的私有空间
CPU亲缘性, CPU粘合
为了更大限度地利用CPU, 保证CPU优先执行该进程/线程, 进程和线程都可以粘合
int num = sysconf(_SC_NPROCESSORS_CONF);
加锁的粒度
在加锁的时候需要考虑粒度的问题, 只针对需要加锁的资源加锁, 其他的坚决不加.
其他
- 为什么会进入稳态?编译的时候设置不优化-o0,count++会出现多线程汇编指令的多线程交叉运行.
- 调用longjmp的函数是没有返回的, 即函数没有出栈