1、什么情况下,需要锁
一行代码翻译若干条指令,加锁将多条指令不被拆分。
2、lock、trylock、spinlock
lock | trylock | spinlock |
线程会休眠 | 线程不会主动休眠 | 线程不会休眠 |
线程A锁住后,线程B再去申请锁,会引起线程B的挂起,会引起线程状态的切换 | 线程B状态有可能被切换 | 线程B状态不会被切换,时间片到了也不会引起状态切换 |
适用于时间长(大于线程切换时间)粒度大 | 适用于时间短(小于线程切换时间)粒度小 |
3、线程切换有两种情况:
1)、运行时,时间片用完,引起切换,被调度时间用完,进入ready状态。
2)、运行中,等待某种条件,如io没有就绪或被锁住,引起的切换,进入wait状态。
4、读写锁
rwlock,两种方式加锁(写加锁、读加锁)、适用于读多写少(读不冲突、写冲突,多线程同时读)的场景,使用方法读时加锁,写时也加锁。
5、原子操作,粒度更小,需要指令系统里有相关指令,才能够实现原子操作 。
不是所有代码都能变成原子操作,例如双向队列,头插法加节点,三行代码实现,如果要原子操作的话,则需要同时对多个地址赋值的指令,但是同时对多个地址进行mov操作的指令系统里是没有的,所以无法实现原子操作。
volatile关键字 内存中易变的变量,CPU每次取该变量的值都会去内存取一遍,而不是直接使用寄存器里的值。
//value++,原子操作
int inc(int *value, int add) {
int old;
__asm__ volatile ( //汇编语法开头写法,构建了原子操作,x86汇编语言
"lock; xaddl %2, %1;" // "lock; xchg %2, %1, %3;" 汇编语句:%2+%1值存储到%1中,
//lock:锁住总线,避免多个CPU操作一个内存块
: "=a" (old)//占位符
: "m" (*value), "a" (add)//%1、%2。
: "cc", "memory"//cpu、高速缓存、主内存。高速缓存和内存之间的一致性问题,MESI四状态、
//高速缓存写回到内存,设置内存屏障不受其他CPU指令干扰操作此内存块。
);
return old;
}
Compare And Swap -->CAS
//无锁 CAS 原子操作
int inc(int *value, int b, int c) {
int old;
__asm__ volatile (
"lock; xchg %2, %1, %3;"
: "=a" (old)
: "m" (*value), "a" (b), "b"(c)
: "cc", "memory"
);
return old;
}
if (a == b)
{
a = c;
}
xchg(instance,NULL,newobject)
6、多进程如何加锁
共享内存,进程操作共享内存,可以将lock、trylock、spinlock、原子操作在共享内存中使用。
int main()
{
int *pcount = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
//mmap只适用于父子进程。匿名方式只适合父子进程。若是使用文件fd,则和父子进程没有关系,可以用在任意进程之间。
int i = 0;
pid_t pid = 0;
for (i=0; i< 10; i++)
{
pid = fork();
if ( pid <= 0 )
{
usleep(1);
break;
}
}
if ( pid > 0 )
{
for (i = 0;i < 100;i ++) //父进程查看
{
printf("count --> %d\n", (*pcount));
sleep(1);
}
} else
{
//子进程进行累加
int i = 0;
while (i++ < 100000)
{
#if 0
(*pcount) ++;
#else
inc(pcount, 1);
#endif
usleep(1);
}
}
}