原子操作概述

有一件事情,你不得不承认,C语言相对汇编来说是高级语言,为什么,因为高级语言会形成封装,比如,我需要把一个变量A++,对于CPU来说,先从内存里把这个变量读进运算寄存器,然后运算增加,然后再把A写入原来的内存位置。

 

什么是原子操作?

不被其他事件打断的操作,就叫做原子操作。

老外是用这样一句话来说明这个

“Do this, and don’t get interrupted while doing this.”

 

我们在CPU里面,想把A++,执行一次,因为是C语言实现的,所以这个指令会被编译成汇编语言,变成了好几个指令,比如CPU从内存中拿A到寄存器里面,这个操作,就可能被其他事件打断,有可能是中断,也可能是定时器,也有可能是CPU线程调度等其他原因。

为什么我们要讨论原子操作?

如果没有多线程编程的同学,应该对这个没概念,也不会知道这个会引起什么问题,这样好了,我来写一个例子。

#include   	
#include   	
#include   	
#include 	
	
#include  	
 	
#define gettidv1() syscall(__NR_gettid)  	
#define gettidv2() syscall(SYS_gettid)	
	
	
long int NUM = 0;	
	
void * th(void * ptr)	
{	
    int i = 0;	
    for(i = 0;i < 1000000000;i++)	
    {	
        NUM++;	
    }	
    //sleep(1);	
    //printf("IM %s ID:%ld number:%ld\n",(char*)ptr,gettidv1(),NUM);	
}	
	
int main(int argc,char ** argv)	
{	
    pthread_t thread1 = -1;	
    if(pthread_create(&thread1,NULL,th,"th1")!=0)	
    {	
        printf("thread1 creat error\n");	
    }	
	
    pthread_t thread2 = -1;	
    if(pthread_create(&thread2,NULL,th,"th2")!=0)	
    {	
        printf("thread1 creat error\n");	
    }	
      	
    	
    while(1)	
    {	
        printf("IM %s ID:%ld number:%ld\n","Main",gettidv1(),NUM);	
        sleep(2);	
    };	
}

 

Makefile文件

all:	
  gcc thread.c -o thread -pthread	
clean:	
  rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions

输出结果

linux@ubuntu:~/linuxBook/linux-c/pthread$ make && ./thread	
gcc thread.c -o thread -pthread	
IM Main ID:7503 number:0	
IM Main ID:7503 number:638925977	
IM Main ID:7503 number:1334751130	
IM Main ID:7503 number:1997474779

 

总结下原因

我相信很多人应该看过类似的例子,但是可能没有自己去写过,而且可能写的时候,给的变量比较小,就有可能导致看到的结果不一样。

我说下原因,因为NUM++ ,这个操作不是原子的,就是说他在干活的时候,有可能被其他东西打断。

 

原子操作概述_第1张图片

线程1在执行第一步的时候,CPU的控制器被调度给线程2使用了,然后呢,线程2 就做自己的事情,把NUM运算成1了,然后CPU控制权又回来到线程1这里,因为线程1刚才上一次的运算还没有完成,他就继续用原来寄存器里面的数据进行运算,然后NUM最后还是1。

可以看到一个问题的是,线程2的这次操作被忽略了,实际上他打工干活了,但是包工头没有给他记账,结果后面结账的时候,也没有给他工钱,这个就比较悲剧了。

在单核CPU上应该很好理解,但是在多核上要费点心思,SMP加上L1,L2缓存后,处理变得很复杂。

之前文章有点长,以后还是简化文章,觉得不错,支持一下。

你可能感兴趣的:(原子操作概述)