我来我网
https://5come5.cn
 
您尚未 登录  注册 | 菠菜 | 软件站 | 音乐站 | 邮箱1 | 邮箱2 | 风格选择 | 更多 » 
 

本页主题: [精彩] 对int变量的赋值是原子[屏蔽]作吗? 显示签名 | 打印 | 加为IE收藏 | 收藏主题 | 上一主题 | 下一主题

zc1984





性别: 帅哥 状态: 该用户目前不在线
头衔: 上帝模式
等级: 荣誉会员
家族: 战略研究所
发贴: 10096
威望: 5
浮云: 0
在线等级:
注册时间: 2004-08-24
最后登陆: 2017-06-08

5come5帮你背单词 [ democratic /demə'krætik/ a. 民主政体的,民主作风的,民众的 ]


[精彩] 对int变量的赋值是原子[屏蔽]作吗?

[精彩] 对int变量的赋值是原子操作吗?


http://www.chinaunix.net 作者:mingjwan  发表于:2006-08-21 08:43:49
发表评论】 【查看原文】 【C/C++讨论区】【关闭

请问对int变量的赋值是原子操作吗?
会不会出现一个线程赋了两个字节,另一个线程来读取数据的情况呢?



 isjfk 回复于:2006-08-04 09:35:10

C 里面没有原子的概念吧,应该是平台相关的。至少在 51 单片机上面,一个 int 赋值就会编译成好几条汇编语句。


 isnowran 回复于:2006-08-04 09:35:41

:)当然不会


 isnowran 回复于:2006-08-04 09:41:51

引用:原帖由 isjfk 于 2006-8-4 09:35 发表
C 里面没有原子的概念吧,应该是平台相关的。至少在 51 单片机上面,一个 int 赋值就会编译成好几条汇编语句。 


老兄,话不能这么讲,否则就没有原子操作了,毕竟一条汇编语句机器还要分解为若干微指令。
原子操作只是协议上的一种保证罢了,保证原子语句在执行期间不会被打断(停电除外),至于这条语句或函数是由多少条组成的,那就不关心了;)


 isjfk 回复于:2006-08-04 09:47:31

引用:原帖由 isnowran 于 2006-8-4 09:41 发表

老兄,话不能这么讲,否则就没有原子操作了,毕竟一条汇编语句机器还要分解为若干微指令。
原子操作只是协议上的一种保证罢了,保证原子语句在执行期间不会被打断(停电除外),至于这条语句或函数是由多少条组 ... 


所以我说在 51 上面 C 的 int 赋值不是原子的,因为肯定有可能会被中断打断一个正在进行的赋值操作。

C 本来就没有原子的概念,这个是跟平台相关的。一般来说 IA32 平台 int 的赋值应该是原子的。


 coldwarm 回复于:2006-08-04 09:50:22

首先,c语言是一种高级语言,他的一条语句可能对应着若干条机器指令。

如果用常量对一个变量进行赋值操作的话,编译器可能会将其转化成一条立即数向存储区传送的指令。

如果是变量对变量赋值的话,由于无法在存储器和存储器之间直接进行传送操作(如果可以的话,指令的长度太长),一般都是使用一个寄存器作为中间的缓冲,这样的话,至少会出现两条机器指令。这种情况下,就不是原子操作了。

在cpu执行指令的过程中,不会出现一条机器指令被中断执行的情况。但是如果有若干条指令的话,就无法保证这些指令的执行是原子性的,除非你使用临界区。


 mingjwan 回复于:2006-08-04 09:55:22

谢谢两位的赐教.那么我想在一般的unix,linux操作系统中.int的赋值应该是原子的了吧.


 dzbjet 回复于:2006-08-04 12:04:54

不会是原子操作吧? 要么还要atomic_t类型干什么?


 思一克 回复于:2006-08-04 12:24:05

对与应用程序的THREAD来说,一个全局的INT的 赋值 应该是原子的。
但一个INT的 用32个循环的位赋值 就不是原子的。

INT的 赋值可能被C搞成多个指令(还会用到寄存器),但最后的一个一定是一个INT操作指令,而不会一个字节一个字节的4次赋值。
既然如此,如果被中断,中断程序会保存运行环境,回来后恢复环境继续。
如果被调度,回来后也会继续。

INT始终是一个整体。数值要么还没有变,要变在一条汇编指令中4个字节一下变完。不可能有一半变了,一半没有变的情况。


 思一克 回复于:2006-08-04 12:54:37

在INTEL 386上,linux内核中,整数赋值 也是原子的。

看atomic_t的定义,就定义为一个整数。
atomic_set()和atomic_read()就是一个赋值汇编语句。没有用lock.

但atomic_add(NNNN, a) 用 LOCK addl NNNN, a;

所以对于应用程序的线程来说, int的赋值,取值一定是原子的。


 doich 回复于:2006-08-08 23:23:34

我有些疑问,不知我的想法是否恰当,还望与各位大侠讨论一下。
我觉得如果单纯定义一个int,对其操作,应该是原子的,但是对于下面这种情况,我觉得还是需要商榷的。
在32位机器上:若有如下结构体定义:
typedef struct{
    char a[2];
    int b;
}test_struct;

int main(int argc, char *argv )
{
    test_struct sTemp;

    memset( sTemp, 0, sizeof( sTemp ) );
    sTemp.b = 5;
   
    return 0;
}

若编译时采用紧缩模式(即非对齐),则我认为对sTemp.b这个int型赋值可能不是原子操作。
因为b的前两个字节紧跟在a[2]后面,第三个字节在位于地址为4的整数倍内存处。
此时,对sTemp.b赋值则需要两次操作,第一次向sTemp.b前两个字节写,第二次向sTemp.b的后两个字节写。(这里说的前后字节暂不考虑大、小端问题,仅指地址上的前后)。
我感觉这就是为什么一般情况下要使用对齐模式的原因,因为对齐后,b的第一个字节就处于4的整数位的地址,CPU取目的操作数时可以一次取到。而 紧缩模式(非对齐)情况下,int型变量恰巧跨越4的整数倍地址,则取目的操作数就需要进行两次操作才能完成,所以可能不是原子的。

我知道我的提法纯属鸡蛋里挑骨头,目的是与大家探讨一下。不当之处希望大家不要砸我。


引用:原帖由 思一克 于 2006-8-4 12:24 发表
对与应用程序的THREAD来说,一个全局的INT的 赋值 应该是原子的。
但一个INT的 用32个循环的位赋值 就不是原子的。

INT的 赋值可能被C搞成多个指令(还会用到寄存器),但最后的一个一定是一个INT操作指令,而 ... 




 mik 回复于:2006-08-09 00:06:48

引用:原帖由 doich 于 2006-8-8 23:23 发表
我有些疑问,不知我的想法是否恰当,还望与各位大侠讨论一下。
我觉得如果单纯定义一个int,对其操作,应该是原子的,但是对于下面这种情况,我觉得还是需要商榷的。
在32位机器上:若有如下结构体定义:
type ... 



不要莽下定义,仔细想想你所说的例子,反汇编代码来看看


 zu_xf 回复于:2006-08-09 08:16:30

别的RISC处理器我不清楚,不过PowerPC的指令长度为32位,因此int型的数值只能拆成高半字和低半字分两次赋值。
   所以说有可能出现写了一半就被打断的情况。


引用:原帖由 思一克 于 2006-8-4 12:24 发表
对与应用程序的THREAD来说,一个全局的INT的 赋值 应该是原子的。
但一个INT的 用32个循环的位赋值 就不是原子的。

INT的 赋值可能被C搞成多个指令(还会用到寄存器),但最后的一个一定是一个INT操作指令,而 ... 




 思一克 回复于:2006-08-09 08:48:54

注意我说的是INTEL X86 LINUX。

某些不得不分次做的笨CPU什么情况都可能。


 isjfk 回复于:2006-08-09 09:10:27

引用:原帖由 思一克 于 2006-8-9 08:48 发表
注意我说的是INTEL X86 LINUX。

某些不得不分次做的笨CPU什么情况都可能。 


我不认为 x86 是什么聪明的 CPU,仅以整形的赋值来决定 CPU 的“聪明”或者“笨”也显然不合适。

[ 本帖最后由 isjfk 于 2006-8-9 09:15 编辑 ]


 思一克 回复于:2006-08-09 09:14:54

考虑PORTING用不同的宏定义。
对与是原子的CPU就用最简单的定义。而不能为了程序万能而用一个万能的定义,这样效率就没有了。

LINUX非常可PORT,它就是对于不同CPU有不同的定义。


 isjfk 回复于:2006-08-09 09:18:31

C 本来就没有保证什么原子性,这个需要程序员来处理。不同的 CPU 对原子性有不同的处理方式。你那个“笨”的定义有意思吗?


 思一克 回复于:2006-08-09 09:27:58

to isjfk,

你说的是什么CPU一个INT分“字节”赋值的?


 zu_xf 回复于:2006-08-09 09:33:04

同意。
PowerPC就是分两次做的,不过这也不能说笨吧。
它的指令长度全部都是32位,在很多方面都有优势。


 isjfk 回复于:2006-08-09 09:37:58

引用:原帖由 思一克 于 2006-8-9 09:27 发表
to isjfk,

你说的是什么CPU一个INT分“字节”赋值的? 


8951 单片机,8位寄存器。有C编译器。上大学的时候弄过一段时间。


 flw 回复于:2006-08-09 09:52:17

整型变量赋值本来就不是原子的,
不过不是原子的倒也不是说它会分字节拷贝。

两个线程共享同一个变量是否需要加锁,
根据需要来定。
但是如果两个线程有可能同时修改这个变量的话,
那么一定是需要加锁的。


 思一克 回复于:2006-08-09 09:53:10

8951单片 肯定比INTEL 586 “笨”了。

我说“笨”没有不好的意思。各有各的用途。


 dzbjet 回复于:2006-08-09 09:56:24

看linux内核中,各个不同cpu平台下atomic_t的定义就知道了。


 flw 回复于:2006-08-09 10:20:48

我刚才写了个例子。
http://svn.perlchina.org/trunk/member/flw/c/demos/thread/is_int_atomic/
如果多个线程同时修改一个变量是原子的,可以不加锁的话,那么这个程序每次运行的结果应该一定是 10000 的整数倍。
可惜它在我的机器上不是。


 思一克 回复于:2006-08-09 10:35:24

to flw,

你的程序很好,我也实验了,结果和你同。

我猜想,你的count++; 这个不是原子的。看atomic.h,
atomic_inc(v)  是带LOCK的:
LOCK "incl %0" 指令。

只有atomic_read, atomic_set(赋值)不用LOCK。


 flw 回复于:2006-08-09 10:45:26

哦,搞错了。
赋值在 x86 上自然是一条指令,那应该是原子的。


 思一克 回复于:2006-08-09 10:46:15

to flw,

我将你程序的count++ 换为count = i

结果唯一9999


 思一克 回复于:2006-08-09 10:49:54

to flw,

但我也糊涂的是 count++ 不是一条吗?我印象应该是汇编INC XXXX一条。
为什么就不行


 flw 回复于:2006-08-09 10:50:52

count ++;
应该不是一条指令。


 思一克 回复于:2006-08-09 10:57:53

to flw,

count++; 也是一条incl   0x8049734

0x08048464 <aaaa+0>:    push   %ebp
0x08048465 <aaaa+1>:    mov    %esp,%ebp
0x08048467 <aaaa+3>:    incl   0x8049734
0x0804846d <aaaa+9>:    leave
0x0804846e <aaaa+10>:   ret


 assiss 回复于:2006-08-09 11:03:50

count++应该是一条指令。
我觉得FLW的程序思路有问题。
如果把线程函数改成:

void *incCount( int id )

{

    int i,j;



printf( "Hello, I am pthread %d\n", id );

for( i=0; i<1000; i++ ){

for(j=0; j<50000;j++)

count++;

}



printf( "[%d] Done.\n", id );

return NULL;

}



则最后结果总为1000×50000×6.

速度慢的机子需要调整i,j值,保证在main printf count之前线程全部执行完毕。

[ 本帖最后由 assiss 于 2006-8-9 11:06 编辑 ]


 assiss 回复于:2006-08-09 11:14:24

我知道FLW的程序错在哪了。
就是线程里的那句:count=0;

当6个线程一起执行的时候,他那里count++刚到888,你这里给它来个归零,全乱套了。


 flw 回复于:2006-08-09 11:23:22

引用:原帖由 assiss 于 2006-8-9 11:14 发表
我知道FLW的程序错在哪了。
就是线程里的那句:count=0;

当6个线程一起执行的时候,他那里count++刚到888,你这里给它来个归零,全乱套了。 


哦,你说的非常之如此有道理,呵呵。
全乱套了。


 dzbjet 回复于:2006-08-09 11:35:48

LOCK前缀好像是对多CPU起作用的,[屏蔽]总线。


 isjfk 回复于:2006-08-09 11:48:47

引用:原帖由 思一克 于 2006-8-9 09:53 发表
8951单片 肯定比INTEL 586 “笨”了。

我说“笨”没有不好的意思。各有各的用途。 


从性能上说的话,别说是 586,恐怕跟 8088 比都好不到哪儿去。

但是如果只是简单的控制电路,51 单片机拿来焊上一个晶振两个电容,接上电源就可以用,x86 CPU 呢?没有一堆的电源电路、振荡电路、北 桥芯片、RAM、ROM 以及复杂的布线等等的跑得起来?从这一点上,即使是最新的 Intel Core Duo 都比 51 单片机要“笨”的多。


 lonelyair 回复于:2006-08-09 12:32:19

to flw
我去掉count = 0;
得到的结果是:count = [1824470000];
是不是能说明是原子的了.


 coco520 回复于:2006-08-09 13:14:04

弱弱地问一句,flw的程序是原子应该是啥结果,不是原子应该是啥结果。


 mingjwan 回复于:2006-08-09 14:12:29

引用:原帖由 lonelyair 于 2006-8-9 12:32 发表
to flw
我去掉count = 0;
得到的结果是:count = [1824470000];
是不是能说明是原子的了. 


是啊.我的结果是
count = [-2143167296]
Done.

我想应该说明不是原子的了.如果是原子的,理论值是60000才对啊.


 思一克 回复于:2006-08-09 14:34:33

不用加。
可以第一个THREAD 将count 设为0x11111111
第二个设22222222,
。。。
第6个设66666666
循环10000次
检查数值,如果数值为这六个数之一,就是原子的,可以保持沉默。
否则打印出来信息,就不是原子的。


 aple_smx 回复于:2006-08-09 14:42:52

在我机器上不是原子的(sun solaris),结果如下:
count = [93969837]
count = [76408895]
count = [93133775]
count = [223344317]
count = [141327456]
(原版)


count = [572662306]
思一克的结论

[ 本帖最后由 aple_smx 于 2006-8-9 14:53 编辑 ]


 思一克 回复于:2006-08-09 15:03:43

flw程序改动版。TEST1, TEST2可以选择。

可以看到原子性。

[CODE]

# include <stdio.h>
# include <stdlib.h>
# include <pthread.h>
# include <unistd.h>

int runFlag = 1;
int count = 0;

#define TEST2

void *incCount( int id );

int main( void )
{
    pthread_t thread;
    int i;
    int ret;

    for( i=0; i<6; i++ ){
        ret = pthread_create( &thread, NULL, (void *(*)(void *))incCount, (void *)i );
        if ( ret != 0 ){
            perror( "pthread_create" );
            exit(-1);
        }
    }

    printf( "Wait 5 seconds...\n" );
    sleep(5);
    printf( "Modify runFlag, stop all work thread.\n" );
    runFlag = 0;
    sleep(1);
    printf( "count = [%d]\n", count );
    printf( "Done.\n" );

    return 0;
}


void *incCount( int id )
{
    int i;
    unsigned int k, v;
    id++;
    k = 0x11111111*id;
    printf( "Hello, I am pthread %d\n", id );
    while(runFlag){
        for( i=0; i<10000; i++ ){
            count = k;

#ifdef TEST1
            v = count;
            if((v & 0x0000ffff) != (v >> 16)) {
                printf("%p %p\n", v, count);

            }
#endif
#ifdef TEST2
           if(count != k)
                printf("%p %p\n", k, count);
#endif
        }
    }

    printf( "[%d] Done.\n", id );
    return NULL;
}

[/CODE]


 isjfk 回复于:2006-08-09 16:18:22

引用:原帖由 思一克 于 2006-8-9 14:34 发表
不用加。
可以第一个THREAD 将count 设为0x11111111
第二个设22222222,
。。。
第6个设66666666
循环10000次
检查数值,如果数值为这六个数之一,就是原子的,可以保持沉默。
否则打印出来信息,就不是原 ... 


这个思路有问题。很可能中间的赋值失败被后面的成功的赋值冲掉。

测试赋值的原子性应该用一种可以把错误放大的方法


 connet 回复于:2006-08-09 16:21:29

这有什么好讨论的.
从原理上讲就是原子性的.
即使机器语言有多条指令, 即使中间被打断, 也不影响其原子性.
对于mips 机器也是原子性的, 中间被打断时, 数据还在寄存器中, 还没复制到变量的地址中, 数据复制到变量的地址中只需要一条机器码.


 思一克 回复于:2006-08-09 16:27:48

to connet,

同意。本来就不是问题。但不是有如此大的误解,才来证明吗


 isjfk 回复于:2006-08-09 16:36:36

to connet,

不同意。本来就不是问题。但不是有如此大的误解,才来证明吗。不是所有的平台都保证整形最终的赋值是一条机器指令:mrgreen:


 思一克 回复于:2006-08-09 16:39:46

to jsjfk,

那你说一个平台(不要说单片呀)的列外并将反汇编的代码贴出来。


 isjfk 回复于:2006-08-09 17:28:15

世界上的正在使用的 CPU 体系结构起码几百种,我能找到所有的都试一遍?我可以证明 linux 源代码中至少有两种 CPU 的 atomic_set 不是简单的 int 赋值而是加了锁。这两种平台我都没有条件测试,虽然不能作为证据,但起码有一定的参考价值。
http://lxr.linux.no/source/include/asm-parisc/atomic.h
http://lxr.linux.no/source/arch/sparc/lib/atomic32.c

这个世界上正在使用的 CPU 不只是 x86,除非有人能够拿出证据证明所有的 CPU 体系结构上的 C 编译器编译出来的整形赋值其机器码 都是原子操作,否则就不能作这样的假设。不要拿“常用的 CPU 都是原子的”作反驳,lz 并没有把问题局限在“常用的 CPU”上面。或许对你来说  x86 就代表整个世界,但是对于很多用单片机工作的人来说,51、AVR、PIC 远远要比 x86 亲切得多。我为什么不能用单片机举例?C 又不 是 x86 的私有玩物


 whyglinux 回复于:2006-08-09 19:25:53

引用:原帖由 mingjwan 于 2006-8-4 09:32 发表
请问对int变量的赋值是原子操作吗?
会不会出现一个线程赋了两个字节,另一个线程来读取数据的情况呢? 



对 int 变量的赋值是否是原子操作与使用的计算机的结构体系有关,同时也与此变量的内存对齐情况有关。

一般的体系结构中都能把一个 int 大小的数据作为一个整体进行处理,也就是说用一条指令完成。对于这样的系统,对 int 变量的赋值是否是 原子操作还取决于此数据在内存中是否已经对齐:如果变量是内存对齐的话,那么就有可能是原子操作;如果非内存对齐,那么一个 int 数据要分至少两次才 能完成,就不是原子操作了。

但是也有的结构体系不是这样的,如 zu_xf 在上面提到的情况,一个 int 数据要拆分为几部分分别处理,对于这样的体系结构,对 int 赋值就不是原子操作了。

如果不是原子操作,就有可能“出现一个线程赋了两个字节,另一个线程来读取数据的情况”。


 _OPEN_ 回复于:2006-08-09 19:29:57

NB,看你的大脑也没有闲着…………………………


 flw2 回复于:2006-08-09 22:55:06

如果不是原子操作,就有可能“出现一个线程赋了两个字节,另一个线程来读取数据的情况”。 

这个能不能解释一下(单CPU),难道一条指令执行了一半.换别的指令(因为要运行其它线程)?


 coldwarm 回复于:2006-08-09 23:52:59

引用:原帖由 flw2 于 2006-8-9 22:55 发表
如果不是原子操作,就有可能“出现一个线程赋了两个字节,另一个线程来读取数据的情况”。 

这个能不能解释一下(单CPU),难道一条指令执行了一半.换别的指令(因为要运行其它线程)? 



考虑比较古老的两种386处理器,80386sx和80386ex,这两种处理器与80386的标准版80386dx的主要区别,就在于它们具有不同的数据总线宽度,前两者都是16位的而后者具有32位数据总线。

无论什么处理器,都是要执行
1。取指令
2。指令译码
3。指令执行。
4。返回1。

都是
通过地址总线来交换存储器的地址或i/o端口的编号
通过控制总线来传递控制信号。
通过数据总线来传递存储单元的内容

这一系列过程中,数据总是要通过数据总线来进行交换,当数据总线只有16位宽时,只能分两次传送才能传递32位数据。


 flw2 回复于:2006-08-10 00:02:06

那这两个是不是连续的对什么人有影响?
如果是16位数据总线分两次取32位的数据,会被分开吗? 如果会还怎么保证正确性/.


 gvim 回复于:2006-08-10 00:26:46

如果把"赋值"操作限定在指令集上,则所有处理器的指令集操作都是原子的,该原子性由硬件提供。

否则,语言层面的"赋值"操作很难说是否是原子的。

比如说一个例子,a, b都放在存储区里:

int a = 10;

int b = 1;

int

main()

{

    b = a;       //赋值

    return 0;

}


那么在X86上的汇编(没有优化)是[gcc 3.3.3]

movl a, %eax

movl %eax, b


显然,虽然两条movl是原子的,但是b = a;这样的赋值操作不是。

而同样的赋值语句b = a;在load-store型的arm上,结果是[gcc3.3.6 arm-cross]

ldr r3, .L2                  #取a的地址,放在r3

ldr r2, [r3, #0]          #[]操作取r3地址里的值,放在r2

ldr r3, .L2+4             #取b的地址,放在r3

str r2, [r3, #0]          #赋值

...

L2:

    .word a

    .word b


这就更明显。由于a,b都是存储区变量,赋值b = a;被翻译成4条指令集语句。

[ 本帖最后由 gvim 于 2006-8-10 00:28 编辑 ]


 mik 回复于:2006-08-10 08:02:57

引用:原帖由 gvim 于 2006-8-10 00:26 发表
如果把"赋值"操作限定在指令集上,则所有处理器的指令集操作都是原子的,该原子性由硬件提供。

否则,语言层面的"赋值"操作很难说是否是原子的。

比如说一个例子,a, b都放在存储区里: ... 




这个说法赞同滴


 isjfk 回复于:2006-08-10 08:39:43

引用:原帖由 whyglinux 于 2006-8-9 19:25 发表
...对于这样的系统,对 int 变量的赋值是否是原子操作还取决于此数据在内存中是否已经对齐:如果变量是内存对齐的话,那么就有可能是原子操作;如果非内存对齐,那么一个 int 数据要分至少两次才能完成,就不是原子操作了...


没有对齐的数据虽然要分两个时钟周期才能获取,但是并不影响这条机器指令的原子性。即使这个时候有中断发生,也是在赋值语句完成之后才会进入中断处理程序。


 isjfk 回复于:2006-08-10 08:41:20

引用:原帖由 flw2 于 2006-8-9 22:55 发表
如果不是原子操作,就有可能“出现一个线程赋了两个字节,另一个线程来读取数据的情况”。 

这个能不能解释一下(单CPU),难道一条指令执行了一半.换别的指令(因为要运行其它线程)? 


机器指令总是具有原子性的,不可能被中断(除非断电或者硬件设计问题)。


 mik 回复于:2006-08-10 08:44:10

引用:原帖由 isjfk 于 2006-8-10 08:39 发表

没有对齐的数据虽然要分两个时钟周期才能获取,但是并不影响这条机器指令的原子性。即使这个时候有中断发生,也是在赋值语句完成之后才会进入中断处理程序。 




能否给个例子,搞段有 “没对齐数据” 的程序来分析一下


 isjfk 回复于:2006-08-10 08:52:45

引用:原帖由 gvim 于 2006-8-10 00:26 发表
如果把"赋值"操作限定在指令集上,则所有处理器的指令集操作都是原子的,该原子性由硬件提供。

否则,语言层面的"赋值"操作很难说是否是原子的。

比如说一个例子,a, b都放在存储区里: ... 


这个说法我认同。


不过我觉得就算赋值语句编译成了两条机器指令,也不影响赋值的原子性。因为就算是两条语句中间被中断打断,然后源数据和目的数据被中断修改,但是返回之后寄存器的值并没有改变,赋值操作继续执行,数据仍然写入了目的地址。这同真正的原子操作结果是一样的。


最糟糕的情况就是赋值之后目的地址里存储的不一定是刚才赋值的数据,对于真正的原子操做这也是一样的。


 isjfk 回复于:2006-08-10 08:55:40

引用:原帖由 mik 于 2006-8-10 08:44 发表



能否给个例子,搞段有 “没对齐数据” 的程序来分析一下 


不用什么程序,看看 Intel CPU datasheet 的时序图就可以理解。而且如果没有逻辑分析仪,用GDB这样的调试器也无法理想的调试“数据没有对齐”的情况。


 whyglinux 回复于:2006-08-10 18:01:59

引用:原帖由 isjfk 于 2006-8-10 08:39 发表

没有对齐的数据虽然要分两个时钟周期才能获取,但是并不影响这条机器指令的原子性。即使这个时候有中断发生,也是在赋值语句完成之后才会进入中断处理程序。 



明白了,谢谢指正。看来我一直在这里存在着错误的认识。自己也写了个小程序,发现即使对没有对齐的 int 访问,在写数据的时候是用一条指令来完成的,而不是我以前误认为的对应着两条写指令。


 Alligator27 回复于:2006-08-10 20:41:59

引用:原帖由 isjfk 于 2006-8-10 08:41 发表

机器指令总是具有原子性的,不可能被中断(除非断电或者硬件设计问题)。 


前面不是有人说, INC/DEC/ADD 指令不是原子的吗?


 mik 回复于:2006-08-10 20:44:18

引用:原帖由 isjfk 于 2006-8-10 08:55 发表

不用什么程序,看看 Intel CPU datasheet 的时序图就可以理解。而且如果没有逻辑分析仪,用GDB这样的调试器也无法理想的调试“数据没有对齐”的情况。 




能否,详细根据 Intel CPU datasheet 来说明一下呢


 思一克 回复于:2006-08-11 10:02:50

TO isjfk,

哈。你不要这样生气吗。对我来说 x86 就代表整个世界,我的见识的确很短浅。

但是我将这个问题的实质说明了,到底为什么是原子的,为什么不是原子的。然后大家可以讨论搞清楚。而一开始你连这一点问题的关键都不懂。根据你立 即的回贴可以看出来。你说一个 int 赋值就会编译成好几条汇编语句就不原子了,这是起码的常识错误呀。说明你对进程(THREAD)切换,环境保存恢 复的知识很少(但你对硬件,但片机等,肯定水平很高),所以才说出这样的话。int 赋值就会编译成好几条汇编语句和原子性没有关系。

恕我直言。

引用:原帖由 isjfk 于 2006-8-4 09:35 发表
C 里面没有原子的概念吧,应该是平台相关的。至少在 51 单片机上面,一个 int 赋值就会编译成好几条汇编语句。 



引用:
这个世界上正在使用的 CPU 不只是 x86,除非有人能够拿出证据证明所有的 CPU 体系结构上的 C 编译器编译出来的整形赋值其机器码 都是原子操作,否则就不能作这样的假设。不要拿“常用的 CPU 都是原子的”作反驳,lz 并没有把问题局限在“常用的 CPU”上面。或许对你来说  x86 就代表整个世界,但是对于很多用单片机工作的人来说,51、AVR、PIC 远远要比 x86 亲切得多。我为什么不能用单片机举例?C 又不 是 x86 的私有玩物 




 isjfk 回复于:2006-08-11 11:03:05

引用:原帖由 思一克 于 2006-8-11 10:02 发表
TO isjfk,

哈。你不要这样生气吗。对我来说 x86 就代表整个世界,我的见识的确很短浅。

但是我将这个问题的实质说明了,到底为什么是原子的,为什么不是原子的。然后大家可以讨论搞清楚。而一开始你连这一点 ... 


...我看是得解释一下

首先我说说“至少在 51 单片机上面,一个 int 赋值就会编译成好几条汇编语句”这句话什么意思。51 单片机的寄存器是 8 位的,但是  C51 的 int 长度是 16 位。这就意味着一个 int 型的赋值(在未经优化的情况下)需要四条汇编指令:(由于年代久远,我已经记不清  51 的汇编怎么写了,就用伪码说明一下。PS:我的硬件水平很低其实,也就可以骗一骗菜鸟 :em06:。看什么看,说你呢,刚才谁说我硬件水平很高 来着 :mrgreen:)

mov    a, 源第一个字节

mov    目的第一个字节, a

mov    a, 源第二个字节

mov    目的第二个字节, a


也就是说一次 int 的赋值对应 4 条机器指令。因为寄存器长度的关系,一个 int 赋值需要分段进行。我的意思不在于一个 int 赋值 最终被编译成几条机器指令,而是对数据的操作是否是整体的。如果不能进行整体的操作显然就需要多条机器指令。这个是我前面没有考虑到这句话可以理解为“先 装载到寄存器,再存储到内存两条指令”的意思。

后面的论战由此而来,我看就不用再解释了。

每条机器指令都是原子操做,所以对于 lz 的问题,本质就归结于 CPU 对于 int 类型长度的数据能否进行整体的操做。如果不能或者不是总能,那就不是原子的。我记得前面说过类似的话。

算啦,我看别再打架了。大家都是读书人,君子动手不动口~ :mrgreen:


其实我是先学明白的汇编语言、数据结构、操作系统原理这些东西才去搞了一段时间硬件,只是因为觉得好玩。现在感觉这一段经历对我的观念影响真的挺大。

[ 本帖最后由 isjfk 于 2006-8-11 11:08 编辑 ]


 isjfk 回复于:2006-08-11 11:07:45

引用:原帖由 mik 于 2006-8-10 20:44 发表

能否,详细根据 Intel CPU datasheet 来说明一下呢 


呵呵,前面我大脑充血说了一些不负责任的话,其实是没经过考证的。回头我看看 Intel CPU 的指令时序图和中断处理时序图,现在手上没资料。


 gvim 回复于:2006-08-11 12:02:57

引用:原帖由 思一克 于 2006-8-11 10:02 发表
int 赋值就会编译成好几条汇编语句和原子性没有关系。点 ... 



能解释下为什么没关系吗?


 思一克 回复于:2006-08-11 12:11:18

比如一个C复制被搞成了5行汇编,
只要最后的一个是整体(整数)复制就可以了。分成100行汇编也没有关系。因为进程调度会保留环境,再运行时接着走。

只有当一个C 整数复制被分字节做,那才有关系。但除了能力十分有限的单片CPU,好象没有这样的。

这个问题不是绝对的。一般是对于一般CPU,单CPU机器说的。


 isjfk 回复于:2006-08-11 12:28:54

引用:原帖由 思一克 于 2006-8-11 12:11 发表
只有当一个C 整数复制被分字节做,那才有关系。但除了能力十分有限的单片CPU,好象没有这样的。


不止是“能力十分有限的单片CPU”吧,我怎么记得有些能力非常强的 CPU 要求对内存数据的操作必须是在对齐的地址上,不对齐的数据需要在程序里分次读入,而不像 x86 那样为程序员着想...


 gvim 回复于:2006-08-11 12:29:29

>>>b = a;
movl a, %eax  //1
movl %eax, b  //2

1,2之间发生中断,会发生不一致。
b = a;被分开了,何来原子?
int是高级语言中的概念,所以"int变量赋值"不是原子的。
OS理论书籍的分析中是把这些基本C语句作为整体来简化讨论的。就像复杂性分析时把这些语句看成O(1)是一样的。
原子性只存在于指令集代码中。在原子性和一致性中,软件方法只能保证一致性(虽然绝大部分机器用机器指令实现一致性功能而不是软件),不能保证原子性。
任意两条指令集代码之间都有可能会被中断。这也是总线锁定指令lock需要存在的原因之一。


 思一克 回复于:2006-08-11 12:31:02

To jsjfk,

对齐的问题我也知道。在linux的众多atomic.h中就可以看到。
我一开始就建议去看这些文件。


 isjfk 回复于:2006-08-11 12:32:48

引用:原帖由 思一克 于 2006-8-11 12:31 发表
To jsjfk,

对齐的问题我也知道。在linux的众多atomic.h中就可以看到。
我一开始就建议去看这些文件。 


-_-|||...


坐不改名行不改姓,我的 ID 是 isjfk......


 思一克 回复于:2006-08-11 12:33:52

TO isjfk  

哈。SORRY,不是COPY的。


 isjfk 回复于:2006-08-11 12:36:20

引用:原帖由 gvim 于 2006-8-11 12:29 发表
>>>b = a;
movl a, %eax  //1
movl %eax, b  //2

1,2之间发生中断,会发生不一致。
b = a;被分开了,何来原子?
int是高级语言中的概念,所以"int变量赋值"不是原子的。
OS理论书籍的 ... 


嗯,严格来说是这样。只不过不管这两条语句间是可能中断还是不会中断,对于下一条语句来说都是一样的。所以粗略地看成是原子的也未尝不可


 思一克 回复于:2006-08-11 12:41:42

中断处理程序不保护寄存器吗?

[QUOTE]

原帖由 gvim 于 2006-8-11 12:29 发表
>>>b = a;
movl a, %eax  //1
movl %eax, b  //2

1,2之间发生中断,会发生不一致。
b = a;被分开了,何来原子?
int是高级语言中的概念,所以"int变量赋值"不是原子的。
OS理论书籍的 ... 



[/QUOTE]


引用:原帖由 isjfk 于 2006-8-11 12:36 发表

嗯,严格来说是这样。只不过不管这两条语句间是可能中断还是不会中断,对于下一条语句来说都是一样的。所以粗略地看成是原子的也未尝不可 




 gvim 回复于:2006-08-11 12:44:40

引用:原帖由 isjfk 于 2006-8-11 12:36 发表

嗯,严格来说是这样。只不过不管这两条语句间是可能中断还是不会中断,对于下一条语句来说都是一样的。所以粗略地看成是原子的也未尝不可 



呵~怎么能这么看?
MP,NUMA等体系的最大实现难度就在于原子和一致,原子和一致为这些多机系统提供了最基本的正确性。然后在保证正确的前提下实现本地优化。
按照你的意思:如果"对于下一条语句来说都是一样"的话,那么multithreading将和uniprocessing没有任何本质区别。


 gvim 回复于:2006-08-11 12:49:01

引用:原帖由 思一克 于 2006-8-11 12:41 发表
中断处理程序不保护寄存器吗?
 



寄存器不是唯一的存储器,或者在多机环境下存储也不是唯一的本地存储器。
两条机器指令间只要有中断的可能,就有发生危险的可能。


 思一克 回复于:2006-08-11 12:54:18

To gvim,

想的太复杂了。问题一般是问的简单的情况,而且是用户程序的多THREAD程序的一个全局变量的复制原子问题。

多机,多CPU,情况当然会有变化。如同一个跑的很好的SERVER程序,如果变到多机甚至多CPU都可能要做PORTING工作,而且还是比较复杂的工作。


 gvim 回复于:2006-08-11 13:07:52

这不是复杂不复杂的问题。即便在up上也如此。

如果a ,b 都是系统级的全局变量
a=10;
b=a;
movl a,%eax   #1
movl %eax, b  #2

1) 进程x 在#1, #2之间发生中断,切换到其他程序y。%eax现在是10,被自动保存。
2) y赋值b=20
3) 切换回x
4) x执行#2句,取出%eax的值10,付给b。
那么,b到底应该是x赋的10还是y赋的20?

问题在于,你能保证#1,#2之间不会发生中断?


 mq110 回复于:2006-08-11 13:11:21

在操作系统的基本课程中早就提到过了 , 

int变量的赋值操作如果翻译成汇编语句不是单条指令的话,那么肯定不是原子的.


 gvim 回复于:2006-08-11 13:14:04

引用:原帖由 mq110 于 2006-8-11 13:11 发表
在操作系统的基本课程中早就提到过了 , 

int变量的赋值操作如果翻译成汇编语句不是单条指令的话,那么肯定不是原子的. 



正解


 mik 回复于:2006-08-11 13:18:21

引用:原帖由 gvim 于 2006-8-11 12:29 发表
>>>b = a;
movl a, %eax  //1
movl %eax, b  //2

1,2之间发生中断,会发生不一致。
b = a;被分开了,何来原子?
int是高级语言中的概念,所以"int变量赋值"不是原子的。
OS理论书籍的 ... 




在你举的赋值语句之间,发生中断的可能性是非常之小的,
   我的印象中,总的来说,中断为二类:1、软中断,由软件调用 int 指令实现; 2、硬件中断,又可以归纳为二类:可屏蔽的中断和不可屏蔽的中断。硬件中断需要某一条件才发生。

   基于中断类型来看,要在赋值语句之间发生中断,那么,软中断和可屏中断不太可能会发生吧。不可屏蔽中断,有一些是CPU内部产生的一些中断,如:0号除零中断,3号debug中断等,有一些是硬件中断,如:时间中断。

除零中断和debug中断就不用说了,赋值语句不会发生的。

中断指令的时钟周期远远大于mov语句,所以,赋值语句之间不太可能中断情形!


 mq110 回复于:2006-08-11 13:19:59

要知道,在计算机的内部,概率很低的事情是经常会发生的,因为运算速度很快.


 思一克 回复于:2006-08-11 13:20:02

to gvim,

应用程序。中断处理不是用户编的,也不会再去改被复制的变量。

你说的情况在KERNEL中当然会存在。所以才有那么多锁之类的东西。而且还有注意许多可以做的和不可以做了。你可以看看KERNEL中的 atomic_t 的复制,但也没有你说的情况(我并不是十分了解)。比如在时钟中断中去故意修改KERNEL中的某些全局atomic,会如何?


 思一克 回复于:2006-08-11 13:20:17

to gvim,

应用程序。中断处理不是用户编的,也不会再去改被复制的变量。

你说的情况在KERNEL中当然会存在。所以才有那么多锁之类的东西。而且还有注意许多可以做的和不可以做了。你可以看看KERNEL中的 atomic_t 的复制,但也没有你说的情况(我并不是十分了解)。比如在时钟中断中去故意修改KERNEL中的某些全局atomic,会如何?


 mq110 回复于:2006-08-11 13:21:14

引用:原帖由 思一克 于 2006-8-11 13:20 发表
to gvim,

应用程序。中断处理不是用户编的,也不会再去改被复制的变量。

你说的情况在KERNEL中当然会存在。所以才有那么多锁之类的东西。而且还有注意许多可以做的和不可以做了。你可以看看KERNEL中的atomi ... 



atomic 是硬件实现的.  而应用层int 赋值未必这么做.


 思一克 回复于:2006-08-11 13:27:11

TO gvim,

一个
i = j; 语句是这样的 

0x08048379 <func1+3>:   mov    0x8049584,%eax
0x0804837e <func1+8>:   mov    %eax,0x8049588

在多THREAD的程序中原子性有问题吗?两个mov之间中断也没有问题吧。系统调度更没有问题。关键是你可以写一个可以证明出问题的程序演示出来。


 mq110 回复于:2006-08-11 13:27:20

而且此类问题,在进程间切换的时候也会发生,当两个进程共享一个int 型的变量的时候.

寄存器的赋值 和 对内存的修改不能一条指令完成的时候, 就要加锁. 因为对int型的变量操作不是原子的.

这也是共享内存的时候要用信号量的原因.


 mik 回复于:2006-08-11 13:35:43

引用:原帖由 mq110 于 2006-8-11 13:27 发表
而且此类问题,在进程间切换的时候也会发生,当两个进程共享一个int 型的变量的时候.

寄存器的赋值 和 对内存的修改不能一条指令完成的时候, 就要加锁. 因为对int型的变量操作不是原子的.

这也是共享内存的时 ... 




加 LOCK  是用在多CPU的系统上, 另一CPU 用修改,系统shard内存, 需要LOCK,锁总线


 flw 回复于:2006-08-11 13:44:41

我支持 mq110 和 gvim 的说法。
不过我之前写的那个程序并不好,所以我现在不再就这个话题说什么了。


 思一克 回复于:2006-08-11 14:01:40

这个程序也是来自flw的改动。在SERVER永远不停。只要用不原子的情况就有输出,否则就什么也不说。
可以监视1个月。

再有看这样监视是否合理,还有更好的改进?

[CODE]

# include <stdio.h>
# include <stdlib.h>
# include <pthread.h>
# include <unistd.h>

int runFlag = 1;
int count = 0;

#define TEST1

void *incCount( int id );

int main( void )
{
    pthread_t thread;
    int i;
    int ret;

    for( i=0; i<6; i++ ){
        ret = pthread_create( &thread, NULL, (void *(*)(void *))incCount, (void *)i );
        if ( ret != 0 ){
            perror( "pthread_create" );
            exit(-1);
        }
    }
    while(1) sleep(100);

    return 0;
}


void *incCount( int id )
{
    int i;
    unsigned int k, v;
    id++;
    k = 0x11111111*id;
    printf( "Hello, I am pthread %d\n", id );
    while(1){
        for( i=0; i<10000; i++ ){
            count = k;
            v = count;
            if((v & 0x0000ffff) != (v >> 16)) {
                printf("%p %p\n", v, count);
            }
        }
    }

    printf( "[%d] Done.\n", id );
    return NULL;
}

[/CODE]


 isjfk 回复于:2006-08-11 14:16:31

引用:原帖由 gvim 于 2006-8-11 13:07 发表
这不是复杂不复杂的问题。即便在up上也如此。

如果a ,b 都是系统级的全局变量
a=10;
b=a;
movl a,%eax   #1
movl %eax, b  #2

1) 进程x 在#1, #2之间发生中断,切换到其他程序y。%eax现在是10,被自动保存。
2) y赋值b=20
3) 切换回x
4) x执行#2句,取出%eax的值10,付给b。
那么,b到底应该是x赋的10还是y赋的20?

问题在于,你能保证#1,#2之间不会发生中断?



对于代码(#3表示任何操作 a 或者 b 的语句):

a=10;

b=a;

movl a,%eax   #1

movl %eax, b  #2

movl x, x     #3


来说,不管 #1 和 #2 之间可能发生中断还是 #1 和 #2 之间不会发生中断,对于 #3 来说都是一样的,即:

    [*]不能认为 b 的值就是 10
    [*]不能认为 a 的值仍然是 10

因为就算 #1 和 #2 构成原子操做,中间不会被打断,也无法保证 #2 和 #3 之间不会发生中断。对于 #3 来说,a 和 b 的值总是可能会被中断程序修改。


 mik 回复于:2006-08-11 14:27:35

引用:原帖由 isjfk 于 2006-8-11 14:16 发表


对于代码(#3表示任何操作 a 或者 b 的语句):

a=10;

b=a;

movl a,%eax   #1

movl %eax, b  #2

movl x, x     #3


来说,不管 #1 和 #2 之间可能发生中断还是 #1 和 #2 之间不会发生中 ... 




我认为: 对共用的存储区,有可能被修改,对私用的存储区不会被修改.


 isjfk 回复于:2006-08-11 14:31:57

引用:原帖由 mik 于 2006-8-11 13:18 发表



在你举的赋值语句之间,发生中断的可能性是非常之小的,
   我的印象中,总的来说,中断为二类:1、软中断,由软件调用 int 指令实现; 2、硬件中断,又可以归纳为二类:可屏蔽的中断和不可屏蔽的中断。硬件 ... 


这个问题不能用可能性非常小来解释。而是在这种并不友好的硬件机制上实现多进程的一致性是可行的。随便找一本 OS 原理书都有这方面的论述。


 isjfk 回复于:2006-08-11 14:37:06

引用:原帖由 isjfk 于 2006-8-11 14:31 发表

这个问题不能用可能性非常小来解释。而是在这种并不友好的硬件机制上实现多进程的一致性是可行的。随便找一本 OS 原理书都有这方面的论述。 


引用:原帖由 mik 于 2006-8-11 14:27 发表

我认为: 对共用的存储区,有可能被修改,对私用的存储区不会被修改. 


是的。对于用一个变量协调两个进程的同步情况来说,就要做这个假设,即只有需要进行协调的两个进程可以修改这个变量。这样就可以肯定这个变量里的 值不是 A 进程赋的值,就是 B 进程赋的值。在这个理论基础上可以实现两个进程的同步。如果有第三个进程干扰,同步肯定失败。


 mik 回复于:2006-08-11 14:45:48

呵呵~ 这个贴子的许多[屏蔽]都是属于纸上谈兵类~

要做到令人信服, 恐怕要深入细致地了解系统对多线程,多进程的实现, 并能举出例子, 还有一个可行的分析、调试的方法


 assiss 回复于:2006-08-11 14:53:59

弱弱地问一句:什么是原子的?
你们争论的让人觉得似乎不是同一个问题。

系统调用是原子的吗?

另外,思一克版主在第9页的程序思路也有些问题:
引用:
count = k;
v = count;
if((v & 0x0000ffff) != (v >> 16)) {
这个不等,恐怕只会出现在按字节赋值等情况下(比如51单片机等int与地址位数不等)----无论count被怎么改,无论v被怎么改,最后v肯定属于0x11111111*[1,6],v & 0xffff 肯定 等于v>>16.




 isjfk 回复于:2006-08-11 14:54:06

引用:原帖由 mik 于 2006-8-11 14:45 发表
呵呵~ 这个贴子的许多[屏蔽]都是属于纸上谈兵类~

要做到令人信服, 恐怕要深入细致地了解系统对多线程,多进程的实现, 并能举出例子, 还有一个可行的分析、调试的方法 


是呀。不过这看起来并不妨碍一群人讨论的热情 :mrgreen:


 flw 回复于:2006-08-11 14:57:31

引用:原帖由 assiss 于 2006-8-11 14:53 发表
弱弱地问一句:什么是原子的?
你们争论的让人觉得似乎不是同一个问题。

系统调用是原子的吗?

另外,思一克版主在第9页的程序思路也有些问题:
 


我也觉得好像不是一个问题。


 isjfk 回复于:2006-08-11 15:02:26




 whyglinux 回复于:2006-08-11 15:04:32

引用:原帖由 mq110 于 2006-8-11 13:11 发表
在操作系统的基本课程中早就提到过了 , 

int变量的赋值操作如果翻译成汇编语句不是单条指令的话,那么肯定不是原子的. 



一个 C 语句通常对应着一条或者多条指令。如果对应着多条指令,那么这个赋值操作就有可能从中间被中断,这时可以说赋值操作不是原子的。这是一种从指令集角度上的理解。

还可以有另外一种理解,即从赋值操作的实际效果上来考虑问题:只要不会出现楼主所说的部分赋值现象,我们就可以说赋值是原子操作。为此,可以把赋 值操作的指令集分为两部分:赋值准备指令和写指令(如果是立即数的赋值可能不需要赋值准备指令)。显然,如果写指令只有一条的话,赋值操作是原子的;否 则,写指令多于一条的话就不能保证赋值操作的原子性了。

楼主所提问的应该是后一种理解,也应该是一般意义上的理解。

>> 寄存器的赋值 和 对内存的修改不能一条指令完成的时候, 就要加锁. 因为对int型的变量操作不是原子的.

如果是这样的话,就无法解释在一些平台上出现的 思一克 所说的下面的情况:
看atomic_t的定义,就定义为一个整数。

atomic_set()和atomic_read()就是一个赋值汇编语句。没有用lock.




 cellar 回复于:2006-08-11 15:14:33

发现克版主是个福将,上任以来,C版的贴子一改以往的低水准,又逐渐回升到一定的层次上来了...这样才能把大牛们圈回来嘛


 mq110 回复于:2006-08-11 15:16:03

看看i386平台对atomic_t类型的定义吧.


typedef struct { volatile int counter; } atomic_t;

有volatile.. 

你们讨论的int counter有volatile吗??? 编译器不会为你们做那些事情的,把int都加上volatile...对不?

所以volatile int counter; 和int counter是两码事.


 mq110 回复于:2006-08-11 15:16:55

不要把int  和atomic_t混淆...

不要认为编译器会为你加上volatile


 isjfk 回复于:2006-08-11 15:19:44

引用:原帖由 mq110 于 2006-8-11 15:16 发表
不要把int  和atomic_t混淆...

不要认为编译器会为你加上volatile 


volatile 意思是不要对其进行优化,总是把变量放在内存里。

不然可能一个进程把变量优化在寄存器里,另一个进程再怎么死改这边也不会知道。这个跟前面讨论的问题没有多大联系。

volatile 不是要给它加锁的意思

[ 本帖最后由 isjfk 于 2006-8-11 15:23 编辑 ]


 mq110 回复于:2006-08-11 15:23:01

引用:原帖由 isjfk 于 2006-8-11 15:19 发表

volatile 意思是不要对其进行优化,总是把变量放在内存里。

不然可能一个进程把变量优化在寄存器里,另一个进程再怎么死改这边也不会知道。这个跟前面讨论的问题没有多大联系。 



这不就是在讨论 int 赋值原子性吗?? volatile 起到的就是不优化,自然也是为了保证原子性?
难道一直所说的int 赋值都是在寄存器里的??


 isjfk 回复于:2006-08-11 15:24:39

引用:原帖由 mq110 于 2006-8-11 15:23 发表


这不就是在讨论 int 赋值原子性吗?? volatile 起到的就是不优化,自然也是为了保证原子性?
难道一直所说的int 赋值都是在寄存器里的?? 


放在寄存器里的话那就更简单了,一条指令解决战斗,什么原子性不原子性的就不用讨论了 :mrgreen:


 mq110 回复于:2006-08-11 15:25:49

引用:原帖由 isjfk 于 2006-8-11 15:24 发表

放在寄存器里的话那就更简单了,一条指令解决战斗,什么原子性不原子性的就不用讨论了 :mrgreen: 




是啊,现在不就是讨论的这个吗??:mrgreen:

我说错了??:em06:


 isjfk 回复于:2006-08-11 15:27:51

引用:原帖由 mq110 于 2006-8-11 15:25 发表



是啊,现在不就是讨论的这个吗??:mrgreen:

我说错了??:em06: 


现在不是在讨论赋值编译成两条机器指令是不是原子的问题吗 :em06:


乱了乱了...... :em06::em06::em06:


 mq110 回复于:2006-08-11 15:31:46

引用:原帖由 isjfk 于 2006-8-11 15:27 发表

现在不是在讨论赋值编译成两条机器指令是不是原子的问题吗 :em06:


乱了乱了...... :em06::em06::em06: 



哦,这样啊。 那还需要讨论吗 ???
都两条指令了.....


 isjfk 回复于:2006-08-11 15:38:18

前面说了,看帖去 :mrgreen:


 gvim 回复于:2006-08-11 15:44:38

引用:原帖由 whyglinux 于 2006-8-11 15:04 发表


一个 C 语句通常对应着一条或者多条指令。如果对应着多条指令,那么这个赋值操作就有可能从中间被中断,这时可以说赋值操作不是原子的。这是一种从指令集角度上的理解。

还可以有另外一种理解,即从赋值操 ... 



实际效果是什么效果?什么是原子?
原子的定义就是不可被打断的操作/实物
物理上的定义就是不可再分的物体,虽然这个定义被找到更小的粒子而模糊。
而操作上的原子 它的意义也就是不可再分的操作。可以被中断了,怎么能说是原子呢?


int b, i;

main()

{

  ....

    pthread_create(&thread, NULL, (void *(*)(void *))incCount1, (void *)r);

    pthread_create(&thread, NULL, (void *(*)(void *))incCount2, (void *)r);

   ....

}

void *incCount1( int id )

{   

    while(1){

        i = 10;

    }

}



void *incCount2( int id )

{   

    while(1){

        i = 50;

        b = i;

        if (b!=i)

        {   

            printf("crashed\n");

            exit(1);

        }

    }

}


这段程序很简单,主要看b被赋值后是否是i。当然i = 50;b = i; if 这3条语句任何时候都有可能被打断。
看asm,主要就是两个循环,所以踢掉了不必要信息。

incCount1:

        ...

.L24:

        movl    $10, i

        jmp     .L24

        ...



incCount2:

        ...

.L28:

        movl    $50, i          #1

        movl    i, %eax       #2

        movl    %eax, b      #3

        movl    b, %eax      #4

        cmpl    i, %eax       #5

        je      .L28

        ...



incCount1地循环里只有movl    $10, i 一句,是原子的。所以可以简单的认为只要运行了incCount1,i 的值必然变成10.
incCount2的循环里有5句,每两句之间标记为@12, @23, @34, @45 。
我们需要比较的是b = i 之后 b =? i,简单起见假设incCount2的一次循环中只可能发生一次线程切换(如果发生2次以上的话,效果更佳)
#2和#3是b = i的代码。

那么:
如果切换发生在#1之前,再换到incCount2的时候,i = 10,继续执行#1,i = 50,由于上面的假设,则后面的 if 不成立,即 b == i。
如果切换发生在@12,那么i = 10,由于上面的假设,后面的 if 也不会成立。
如果切换发生在@23,那么i = 10,由于%eax被自动保存,那么%eax = b = 50,则后面的 if 成立。
如果切换发生在@34,那么i = 10,b = 50,则后面的 if 成立。
如果切换发生在@45,那么i = 10,b = 50,则后面的 if 成立。

可以看到在@23, @34, @45 三处地方发生切换的话,b 是不等于 i 的。
发生在@34、@45处的切换说明了不一致性的存在。
中断既然可以发生在@34、@45处,为什么不能发生在@23处?
发生在@23处的切换说明了b = i 的赋值并不是不可打断的。

>>>如果写指令只有一条的话,赋值操作是原子的
在可以使 if 不成立的 3 处地方,只有一条对变量的写指令,即movl    %eax, b。按照你的说法就是原子了,但是 b != i 却发生了。

看来不少人对"原子性"这个概念的理解问题很大啊:mrgreen:


 kuaizaifeng 回复于:2006-08-11 15:44:53

对于中断,CPU会有现场保护的
不过有部分单片机似乎没有现场保护


 mq110 回复于:2006-08-11 15:46:27

引用:原帖由 whyglinux 于 2006-8-11 15:04 发表

看atomic_t的定义,就定义为一个整数。



第一.

我这里说volatile,是因为 whyglinux大人说 atomic_t定义为一个整数..我提出了反驳..

第二.

多进程操作共享内存,哪怕就是一个int 也要加信号量,具体的细节可以去看操作系统的书..
而且如果我没记错的话,stevens在他的Unix网络编程上 也是给共享内存所操作的加信号量,或者互斥锁是非常明确的.


第三.

两条指令首先就不是原子的了..
说int 赋值语句翻译成汇编语句为两条指令.  并且该变量为多进程或多线程的全局变量,那么一定存在进程被切换的情况。不能保证原子.

以上是我的观点.


 gvim 回复于:2006-08-11 15:54:04

引用:原帖由 思一克 于 2006-8-11 13:27 发表
TO gvim,

一个
i = j; 语句是这样的 

0x08048379 <func1+3>:   mov    0x8049584,%eax
0x0804837e <func1+8>:   mov    %eax,0x8049588

在多THREAD的程序中原子性有问题吗?两个mov之间中 ... 



关键在于,i = j ,你不能保证执行之后 i 就一定等于 j,例子在上面给出了。
而原子性的作用就在于此:只要你执行了这个操作,保证就是你要的结果。


 思一克 回复于:2006-08-11 15:57:59

原子性就是从使用者角度不用加琐来保证数据的一致性。
至于中间是否有中断打断不用管。

谈系统调用的原子性就是这含义。比如write,有数百上千汇编指令,但在许多时候都可以说是原子的。


 mik 回复于:2006-08-11 15:58:10

引用:原帖由 gvim 于 2006-8-11 15:54 发表


关键在于,i = j ,你不能保证执行之后 i 就一定等于 j,例子在上面给出了。
而原子性的作用就在于此:只要你执行了这个操作,保证就是你要的结果。 




i = j; 什么时候下不是原子的?

要执行什么操作来保证其原子性?


 kuaizaifeng 回复于:2006-08-11 15:59:41

引用:原帖由 思一克 于 2006-8-11 15:57 发表
原子性就是从使用者角度不用加琐来保证数据的一致性。
至于中间是否有中断打断不用管。

谈系统调用的原子性就是这含义。比如write,有数百上千汇编指令,但在许多时候都可以说是原子的。 



对,我觉得原子的含义就是这样的


 isjfk 回复于:2006-08-11 16:01:57

引用:原帖由 gvim 于 2006-8-11 15:54 发表


关键在于,i = j ,你不能保证执行之后 i 就一定等于 j,例子在上面给出了。
而原子性的作用就在于此:只要你执行了这个操作,保证就是你要的结果。 


就你前面的程序来说吧,假设在每一个赋值语句两边都加了锁。用你的观点,现在都是原子的了对吧。

但是后边的 if 一样可能成立。因为中断会发生在任何时候。对于下一条语句来说,前面的结果总是不可靠的。所以我说从效果上来说,原子与否其实都一样。

当然如果你从理论上深究,那确实不是理论上的原子性。但理论总是要拿来用的

[ 本帖最后由 isjfk 于 2006-8-11 16:03 编辑 ]


 mik 回复于:2006-08-11 16:04:18

引用:原帖由 isjfk 于 2006-8-11 16:01 发表

就你前面的程序来说吧,假设在每一个赋值语句两边都加了锁。用你的观点,现在都是原子的了对吧。

但是后边的 if 一样可能成立。因为中断会发生在任何时候。对于下一条语句来说,前面的结果总是不可靠的。所以 ... 



:lol: 打断一下,你所说的加锁是怎么个加锁法?


 gvim 回复于:2006-08-11 16:09:55

引用:原帖由 思一克 于 2006-8-11 15:57 发表
原子性就是从使用者角度不用加琐来保证数据的一致性。
至于中间是否有中断打断不用管。

谈系统调用的原子性就是这含义。比如write,有数百上千汇编指令,但在许多时候都可以说是原子的。 



呵,LZ的问题是"对int变量的赋值是原子操作吗"。我的回答是“不是”。
从字节赋值的角度看,我并不清楚是否大部分常见的赋值都是32位一次的(或者在非对其的情况下需要两次赋值),所以我没有给出任何答案。在ARM中,short, char等的赋值操作并不像int那样简单。
从下面isjfk 开始有部分争论存在于"多条语句的赋值是否仍然是原子的",基于此我发表了自己的见解。

末了,既然要往偏里掰,我也就不接招了。:D


 isjfk 回复于:2006-08-11 16:11:08

引用:原帖由 mik 于 2006-8-11 16:04 发表


:lol: 打断一下,你所说的加锁是怎么个加锁法? 


比如在 b = i; 之前同时在 b 和 i 上加锁。别的线程如果要访问 b 或者 i 则要首先测试是否加锁。这样保证 b = i; 是不可打断的,也就是原子的喽~


 isjfk 回复于:2006-08-11 16:13:38

引用:原帖由 gvim 于 2006-8-11 16:09 发表
从下面isjfk 开始有部分争论存在于"多条语句的赋值是否仍然是原子的",基于此我发表了自己的见解。


冤枉哪,“多条语句的赋值是否是原子的”这是小思引发的争论 :em06:


 gvim 回复于:2006-08-11 16:14:09

引用:原帖由 思一克 于 2006-8-11 15:57 发表
原子性就是从使用者角度不用加琐来保证数据的一致性。
至于中间是否有中断打断不用管。

谈系统调用的原子性就是这含义。比如write,有数百上千汇编指令,但在许多时候都可以说是原子的。 



这个原子性的定义是不对的。


 gvim 回复于:2006-08-11 16:17:23

引用:原帖由 isjfk 于 2006-8-11 16:01 发表

就你前面的程序来说吧,假设在每一个赋值语句两边都加了锁。用你的观点,现在都是原子的了对吧。

但是后边的 if 一样可能成立。因为中断会发生在任何时候。对于下一条语句来说,前面的结果总是不可靠的。所以 ... 



你是怎么加锁的?

我得意思是
lock();
b = i;
unlock();

lock()
movl i, %eax
movl %eax, b
ulock()

不错,if 仍然会发生,不过那是@34和@45的情况了,而不是@23的情况。
对于原子性,在执行 b=i 的时候,我可以肯定的告诉你,在执行完这条 b=i 之后,b一定就是i,并且就是我要的i,i 不可能在中间变化。
如果还不理解,只有去k书了,我只能说到这个份上了:mrgreen:


 mq110 回复于:2006-08-11 16:19:00

至此为止,我同意gvim


 isjfk 回复于:2006-08-11 16:21:45

引用:原帖由 gvim 于 2006-8-11 16:17 发表
你是怎么加锁的?

我得意思是
lock();
b = i;
unlock();

lock()
movl i, %eax
movl %eax, b
ulock()

不错,if 仍然会发生,不过那是@34和@45的情况了,而不是@23的情况。
对于原子性,在执行 b=i 的时候,我可以肯定的告诉你,在执行完这条 b=i 之后,b一定就是i,并且就是我要的i,i 不可能在中间变化。
如果还不理解,只有去k书了,我只能说到这个份上了:mrgreen:


我也是差不多的意思。如果 b = i 是原子的,那 b = i 之后显然 b 和 i 是一样的,但是对于 b = i 的下一条语句来说,b 未必就等于 i,因为两条语句中间可能会发生中断。

既然对于下一条语句来说,b 是不是等于 i 总是无法保证的,那原子性又有什么意义呢 :mrgreen:


 gvim 回复于:2006-08-11 16:30:14

引用:原帖由 isjfk 于 2006-8-11 16:21 发表

我也是差不多的意思。如果 b = i 是原子的,那 b = i 之后显然 b 和 i 是一样的,但是对于 b = i 的下一条语句来说,b 未必就等于 i,因为两条语句中间可能会发生中断。

既然对于下一条语句来说,b 是不是等 ... 



引用:An atomic operation in computer science refers to a set of operations that can be combined so that they appear to the rest of the system to be a single operation.

Conditions
To accomplish this, two conditions must be met:

   1. Until the entire set of operations completes, no other process can know about the changes being made; and 
   2. If any of the operations fail then the entire set of operations fails, and the state of the system is restored to the state it was in before any of the operations began. 

To the rest of the system, it appears that the set of operations either succeeds or fails all at once. No in-between state is accessible. This is an atomic operation.


from http://en.wikipedia.org/wiki/Atomic_operation

你管它下一条做什么??:em06:
>>>be a single operation
原子性对应的是一个操作,而不是两个操作。
>>>No in-between state is accessible
是否@23时的情况就是在中途窃取了 i 的值?


 isjfk 回复于:2006-08-11 16:36:17

引用:原帖由 gvim 于 2006-8-11 16:30 发表



from http://en.wikipedia.org/wiki/Atomic_operation

你管它下一条做什么??:em06:
>>>be a single operation
原子性对应的是一个操作,而不是两个操作。
>>>No in-bet ... 


:em06:

是的,从理论上是你说的那样。但是实际上对 int 型赋值的操作即使中间被打断也没有影响。所以 linux 可以无法无天的直接用一个整形赋值就定义了 atomic_set 而没有加任何的锁(哦,是在绝大部分平台上)。

我看争论大概可以结束了,话都说明白了,再争下去也没什么新鲜的啦 :mrgreen:


 mik 回复于:2006-08-11 16:46:18

引用:原帖由 gvim 于 2006-8-11 16:17 发表


你是怎么加锁的?

我得意思是
lock();
b = i;
unlock();

lock()
movl i, %eax
movl %eax, b
ulock()

不错,if 仍然会发生,不过那是@34和@45的情况了,而不是@23的情况。
对于原子性,在执 ... 




请问:这个lock()怎么实现呢?


 mq110 回复于:2006-08-11 16:47:16

引用:原帖由 isjfk 于 2006-8-11 16:36 发表

:em06:

是的,从理论上是你说的那样。但是实际上对 int 型赋值的操作即使中间被打断也没有影响。所以 linux 可以无法无天的直接用一个整形赋值就定义了 atomic_set 而没有加任何的锁(哦,是在绝大部分平台上 ... 




是这样吗???


 mq110 回复于:2006-08-11 16:48:09

引用:原帖由 mik 于 2006-8-11 16:46 发表



请问:这个lock()怎么实现呢? 



多进程是信号量实现的。


 isjfk 回复于:2006-08-11 16:50:37

引用:原帖由 mq110 于 2006-8-11 16:47 发表



是这样吗??? 


嗯,这个我是看了才说的 :mrgreen:

[ 本帖最后由 isjfk 于 2006-8-11 16:51 编辑 ]


 mik 回复于:2006-08-11 16:51:59

引用:原帖由 mq110 于 2006-8-11 16:48 发表


多进程是信号量实现的。 



这就与LZ所讨论的问题离题千里:mrgreen:


 isjfk 回复于:2006-08-11 16:55:14

引用:原帖由 mq110 于 2006-8-11 16:48 发表


多进程是信号量实现的。 


信号量怎么实现的?信号量不是一个变量吗?:mrgreen:


 mq110 回复于:2006-08-11 17:07:32

引用:原帖由 mik 于 2006-8-11 16:51 发表


这就与LZ所讨论的问题离题千里:mrgreen: 



呵呵,确实不是楼主讨论的情况。。


 mq110 回复于:2006-08-11 17:15:11

引用:原帖由 isjfk 于 2006-8-11 16:50 发表

嗯,这个我是看了才说的 :mrgreen: 




难道这不是volatile 起的作用???


 assiss 回复于:2006-08-11 17:16:14

请问,到底什么是原子的?
以系统调用为例,哪个系统调用可以说是原子的?

以write(fd=STDERR_FILENO, buff, len=4)为例,
假设write需要2个mov就可以完成(多么有效率的系统啊)
mov1
...被打断,另一线程执行fd=STDOUT_FILENO,或者len = 8(实际情况要复杂得多,大家都明白)
mov2

这和i=j;有区别吗?


 Alligator27 回复于:2006-08-11 20:51:15

引用:原帖由 assiss 于 2006-8-11 17:16 发表
请问,到底什么是原子的?
以系统调用为例,哪个系统调用可以说是原子的?

以write(fd=STDERR_FILENO, buff, len=4)为例,
假设write需要2个mov就可以完成(多么有效率的系统啊)
mov1
...被打断,另一线程 ... 


他们讨论的是机器底层的原子操作. 及一条或多条指令操作, 从开始到结束, 内存变量保持完整一致. 不受中断和别的CPU竞争的影响. 从外部看, 内存变量要么没变, 要么变成原子操作的结果, 其间不可能有第三种可能.

你说的系统调用read/write的原子性, 是狭义的逻辑的原子操作. 及对所有的应用程序, 看到的, 或能改变的(别的 read/write请求), 要么是write前的内容, 要么是write后的内容, 不可能在中间. 当然kernel是可以看到,甚至更改中间结 果的.

个人理解.


 whyglinux 回复于:2006-08-11 22:03:04

gvim 引用的一段话很好地说明了原子操作的特征:

the set of operations either succeeds or fails all at once. No in-between state is accessible.

然而这两句话可以对赋值操作的原子性产生两种不同的理解(前面已经说过了):一种是赋值操作如果是由多条指令组成的话,由于没有同步保证,可以被 中断,所以赋值过程不是原子的。另一种是从赋值的结果(是否存在中间状态)上来考虑。一些争论就是因为持有不同的 理解造成的。我们更加关心的是结果,而不是过程,所以赋值的原子性一般指的是后一种情况。

考察一个操作的原子性就是考察此操作结果是否会由于中断而出现一个中间状态(in-between state)。

对于赋值来说,如果它是一个原子操作,则说明要么赋值已经成功,要么尚未进行,不存在只进行了一半的所谓中间状态的情况;如果出现了中间状态,则说明赋值不是一个原子操作。

这是从结果(状态)上来说的。至于由一条或者多条指令组成赋值过程是否是原子过程并不重要,关键是看其中 的向赋值操作的左操作数中的写操作是由一条还是几条指令组成的:如果这样的写指令只有一条,则赋值是原子性的;如果多于一条写指令,则在第一次写操作后如 果被中断的话其赋值结果就出现了一个中间状态,因此就不是原子操作。

修改:加上颜色强调一下。

[ 本帖最后由 whyglinux 于 2006-8-12 01:14 编辑 ]


 cellar 回复于:2006-08-11 22:20:23

无论从理论层面上来说,还是操作层面上来说,赋值语句都未必是原子的(个别实现可能是),从理论上前面几位已经说的很清楚了,我想说说应用层面上,不加锁的多线程赋值产生的效果

各位可以随便翻一下经典的多线程编程的书籍,几乎每本都在访问共享变量的时候先加锁,再读写,如果赋值操作在操作层面上来说是原子的(先不管这个 定义是不是准确,为了说明问题),那这些书都是多此一举的吗?因为按照前面几位的观点,即使赋值过程中被打断,也会由硬件或操作系统来做入/出栈的工作, 从而保证赋值在操作层面上是原子的。

事实上不是,如果你曾经写过稍微复杂一点的多线程程序的,就会知道,不加锁的读写共享变量就会导致未知错误,程序或某个进/线程会在不可预知的地点毫无提示的退出,这就是我的经验,坐个马扎等着被拍砖:D


 whyglinux 回复于:2006-08-11 22:59:19

引用:原帖由 cellar 于 2006-8-11 22:20 发表
各位可以随便翻一下经典的多线程编程的书籍,几乎每本都在访问共享变量的时候先加锁,再读写,如果赋值操作在操作层面上来说是原子的(先不管这个定义是不是准确,为了说明问题),那这些书都是多此一举的吗?



注意在这里讨论的是对 int 变量的赋值是否是原子的,而且即使是对 int 变量的赋值,并不是在所 有的平台上都是原子操作,这时就要进行同步保证(比如加锁)。你说的共享变量的访问情况可能是:1. 对 int 的赋值不是原子的。2. 访问的是长度 大于 int 的数据。3. 是对一组变量的访问。上述无论哪种情况都需要同步保证。


 gvim 回复于:2006-08-11 23:08:35

引用:原帖由 whyglinux 于 2006-8-11 22:03 发表
gvim 引用的一段话很好地说明了原子操作的特征:

the set of operations either succeeds or fails all at once. No in-between state is accessible.

然而这两句话可以对赋值操作的原子性产生两种不同的理 ... 



我到第一次听说原子性还可以从过程和结果两种方式去理解。

如你说所,过程不是原子的已经达成共识。

那么我第二次问你“赋值结果”是什么意思?
你的意思是否是 重点在最后的往左操作数b写入的 movl %eax, b 一句是原子的,那么整个 b=i 的赋值操作就是原子的?

你的意思是原子性要么全做,要么全不做?
如上述 b = i;在@23发生中断的时候,赋值源 i 都变了,你还能得到正确的 b 的结果?
赋值到没有进行一半,movl %eax, b肯定是执行了;
不过,如此这般的话,连最起码的正确性都有问题,那还谈什么原子性呢?

[ 本帖最后由 gvim 于 2006-8-11 23:09 编辑 ]


 mik 回复于:2006-08-11 23:34:11

引用:原帖由 gvim 于 2006-8-11 23:08 发表


我到第一次听说原子性还可以从过程和结果两种方式去理解。

如你说所,过程不是原子的已经达成共识。

那么我第二次问你“赋值结果”是什么意思?
你的意思是否是 重点在最后的往左操作数b写入的 movl % ... 



你所认同的原子操作是 CPU 的调度单位,是一条指令的执行,

他们认同的原子操作是 OS 的调度单位,也就是利用信号量,临界点等手段来保证操作的执行。


 gvim 回复于:2006-08-11 23:51:38

引用:原帖由 mik 于 2006-8-11 23:34 发表


你所认同的原子操作是 CPU 的调度单位,是一条指令的执行,

他们认同的原子操作是 OS 的调度单位,也就是利用信号量,临界点等手段来保证操作的执行。 



整篇帖子的讨论 什么时候又把信号量,临界点拖出来了?真是莫名其妙。
"int变量的赋值" 你认为是OS在为变量赋值吗?真是答非所问。
有人说把一条赋值语句分成N条指令后仍然是原子的,但是我的论点里什么时候把原子性特定在CPU里了?我只是说最底层的原子性是硬件提供的,上层再利用下层,上上层再利用上层。

最后,请你运行我111楼的程序片断,如果没有错误的话再来讨论,请不要想当然的给我下定义,好吗?

既然大家认为讨论的不是一个东西,那么对于这篇帖子我不再发表任何技术见解。
不过对于想了解正确答案的朋友,还请自己翻翻书去求证到底可否如思一克和whyglinux两位朋友所认为的那样去想象"int变量的赋值"这个简单论题的原子性/原子操作。
如同前面我对isjfk 朋友说的:我能说的就这么多了。

[ 本帖最后由 gvim 于 2006-8-12 00:25 编辑 ]


 mik 回复于:2006-08-12 00:02:05

引用:原帖由 gvim 于 2006-8-11 23:51 发表


整篇帖子的讨论 什么时候又把信号量,临界点拖出来了?真是莫名其妙。
"int变量的赋值" 你认为是OS在为变量赋值吗?真是答非所问。
有人说把一条赋值语句分成N条指令后仍然是原子的,但是我的论点 ... 




:P

[ 本帖最后由 mik 于 2006-8-12 00:34 编辑 ]


 gvim 回复于:2006-08-12 00:07:56

edited by converse

[ 本帖最后由 converse 于 2006-8-12 00:36 编辑 ]


 gvim 回复于:2006-08-12 00:21:06

edited by converse:mrgreen::mrgreen:

[ 本帖最后由 converse 于 2006-8-12 00:36 编辑 ]


 converse 回复于:2006-08-12 00:23:05

请不要在贴子里面用一些人身攻击的词语,雷[屏蔽]说了:以德服人,好不容易出来一个讨论这么激烈的贴子不要被口水给玷污了,最后把论坛的气氛搞砸了受损的还是大家,所以希望各位适可而止,谢谢....~~~

技术上的好坏还是不要牵扯到人品吧,就事论事不要扯其它的东西为好,水平人品如何公道自在人心,不是你几句话就能说的清编辩的明白的.

[ 本帖最后由 converse 于 2006-8-12 00:26 编辑 ]


 converse 回复于:2006-08-12 00:28:56

done~~:mrgreen::mrgreen:

[ 本帖最后由 converse 于 2006-8-12 00:36 编辑 ]


 converse 回复于:2006-08-12 00:32:17

done~~:mrgreen::mrgreen:

[ 本帖最后由 converse 于 2006-8-12 00:37 编辑 ]


 aero 回复于:2006-08-12 00:50:51

小c辛苦了。


 converse 回复于:2006-08-12 00:52:37

引用:原帖由 aero 于 2006-8-12 00:50 发表
小c辛苦了。 



引用:Lucy可是个好女孩~~


引用:其实,我是一个机会[屏蔽]者~`~


广告时间:小c可是个好小伙~~:mrgreen::mrgreen:

[ 本帖最后由 converse 于 2006-8-12 00:56 编辑 ]


 whyglinux 回复于:2006-08-12 01:00:59

>> 我到第一次听说原子性还可以从过程和结果两种方式去理解。

这是因为对于语言层面上的一个语句来说,如果没有同步保证的话来讨论它的原子性不是很明智。大部分赋值语句可以对应着一系列的指令,这时显然这个赋值操作过程不是原子的,但是就赋值的结果来说(左操作数有没有出现中间值),可能是原子的,也可能不是。

>> 如你说所,过程不是原子的已经达成共识。

严格来说不是这样的,因为如 x = 123; 这样进行的立即数赋值可能只需要诸如 movl    $123, -4(%ebp) 一条指令,所以无论是赋值过程还是结果都是原子性的。

>> 那么我第二次问你“赋值结果”是什么意思?
>> 你的意思是否是 重点在最后的往左操作数b写入的 movl %eax, b 一句是原子的,那么整个 b=i 的赋值操作就是原子的?

单条指令,如 movl %eax, b 的执行不可被中断,所以其操作,无论结果还是过程,都是原子的。

问题在于(前面也已经提到过),一个赋值语句的向左操作数写入的过程可能不是由一条传送指令、而是由两条或者以上的传送指令组成(比如对于  long long int 变量一般需要要两条传送指令完成赋值。虽然不常见,但是有的系统即使对于 int 变量也是这样)。如果是这种情况,显然 赋值不是原子操作。


 isjfk 回复于:2006-08-12 08:08:45

153楼的,你的最后一条显然没有明白 gvim 的意思。不好好看帖,打屁屁 :mrgreen:



再争下去没意思了,只灌水...




PS:怎么还打起来了?君子动手不动口 :mrgreen:  你们这样是不对滴,打伤了人多不好呀。就算没有打到人打到花花草草的也不好呀......

[ 本帖最后由 isjfk 于 2006-8-12 08:16 编辑 ]


 flw2 回复于:2006-08-12 10:36:03

movl $0,%eax
也不是原子的.


 mik 回复于:2006-08-12 10:42:21

引用:原帖由 flw2 于 2006-8-12 10:36 发表
movl $0,%eax
也不是原子的. 



为什么这么说呢?


 flw2 回复于:2006-08-12 10:45:12

引用:原帖由 mik 于 2006-8-12 10:42 发表


为什么这么说呢? 



为什么是原子的?


 mik 回复于:2006-08-12 10:49:24

引用:原帖由 flw2 于 2006-8-12 10:45 发表


为什么是原子的? 



这条指令还能拆分吗?

这个话题已经没什么好讨论的了,只是在这里灌灌水,我STOP了:wink:


 flw2 回复于:2006-08-12 10:56:32

引用:原帖由 mik 于 2006-8-12 10:49 发表


这条指令还能拆分吗?

这个话题已经没什么好讨论的了,只是在这里灌灌水,我STOP了:wink: 


这条指令确实(可能)可以拆分 ,就象a=b,可能可以拆分一样.
我一直想不明白大家讨论怎么会这么多,为了保护一个字节把信号量都弄出来了.


 cellar 回复于:2006-08-12 13:26:01

我前面发的贴子我认为没有whyglinux所说的逻辑问题,变量赋值包括int赋值,这是一个简单命题,既然我认为所有的赋值都不是原子的,也不必再单独说一遍int赋值也不是原子的。

我理解whyglinux的意思是说:因为大多数情况下赋值最终必然是由一条汇编语句完成的,所以这条语句是不可打断的,由此认为int赋值也是原子的。

这是一个明显的逻辑错误嘛。
就象人总是要死的,死的那一瞬间是不能打断的,难道人生是原子的?


 pcanywhere 回复于:2006-08-12 13:55:20

其实很简单。
如果int 赋值指令被翻译成一条指令,这自然是原子的。
否则,则不是原子的。


 aero 回复于:2006-08-12 14:03:10

就算int赋值不是原子的,假设是两条movl指令。那么在这两条movl之间有可能发生什么中断呢?OS的线程调度是发生在这个之间的吗?


 ~~~~ 回复于:2006-08-12 14:17:58

引用:原帖由 aero 于 2006-8-12 14:03 发表
就算int赋值不是原子的,假设是两条movl指令。那么在这两条movl之间有可能发生什么中断呢?OS的线程调度是发生在这个之间的吗? 



都讨论到17页了你还在问这个问题,我服了你了:em02:
你去试试111楼的例子,我得出的结论和gvim一样。那个个例子回答了你的疑问。


 ~~~~ 回复于:2006-08-12 14:22:31

引用:原帖由 flw2 于 2006-8-12 10:36 发表
movl $0,%eax
也不是原子的. 



这样没有常识的结论也敢下,我服了你了。:lol:


 mik 回复于:2006-08-12 15:45:05

回163楼, 忍不住,又插一句::lol:

闲着没事可做,没什么别的意途。

111 楼那程序并不能说明什么, 我将 111 楼那个程序的 inc_count2() ,加了几条 printf() 语句


void * inc_count2(int id)

{

       while(1) {

                printf("i = %d\n, i);                /*    #1     */

                printf("d = %d\n",d);             /*    #2    */





                i = 50;

                b = i;

               

                printf("i = %d\n", i);             /*    #3     */

                printf("d = %d\n",d);           /*     #4     */

                

                if (b != i) {

                         printf("crashed\n");

                         exit(1);

                }

       }

}


结果是:

i = 10

b = 0

... ...



i=50

b=50

i = 50

b = 50



... ...



crashed


在第1,2条 printf 输出了 i = 10  b = 0 的结果
在第2第printf 到 最后 crashed 信息输出之间全都是

i = 50 
b = 50


说明:在两2条 printf() 和 printf("crashed\n") 语句之前,i 和 b 是相等的, 
      经过一些时间后,出现了不相等情况,那是在第4条 printf() 语句之后的事情了。
      i = 50;  b = i;  这两条赋值语句并没有被中断

      那么,这里是否可以初步认为,这两条赋值语句的具有原子性呢


 mik 回复于:2006-08-12 15:47:28

说过 STOP 了,又发一贴,罪过罪过,佛祖别惩罚我:oops:

[ 本帖最后由 mik 于 2006-8-12 15:52 编辑 ]


 ~~~~ 回复于:2006-08-12 17:53:15

引用:原帖由 mik 于 2006-8-12 15:45 发表
回163楼, 忍不住,又插一句::lol:

闲着没事可做,没什么别的意途。

111 楼那程序并不能说明什么, 我将 111 楼那个程序的 inc_count2() ,加了几条 printf() 语句


void * inc_count2(int id)

 ... 





呵呵,好玩。

按照gvim的解释,在111的程序里有3处地方可以改变i,而真正说明问题的只有一种情况。概率为1/3。他也没有办法证明到底某一次切换是发生在这3句中的那一句。

而你的程序,加了两条printf之后,printf翻译成汇编之后是多少句?假设200句汇编吧(不算多吧),两句就是400条,而概率就变为1/403,甚至少。

那你的程序不是更不能说明什么吗?



 mik 回复于:2006-08-12 20:37:15


引用:原帖由 flw2 于 2006-8-12 10:36 发表

movl $0,%eax

也不是原子的. 







flw2,  我仔细琢磨一下。你所说的还是有一定的道理的。



movl $0, %eax  这句指令应该是原子性的。因为它没有存/取内存的行为。



但:

movl %eax, a  这一条指令,是不能保证它一定是原子性的。



CPU 要将数据存放在内存,必须经过地址总线传递。





考虑以下的一种情况:



在多CPU系统中, CPU 发出指令 要将 eax 数据往 [a] 这个内存里放,经过地址总线,



而正好,另一个CPU 抢先把地址总线占了,那么,第一个CPU 执行的指令应该先被挂起,直至地址总线空闲了,它才可以继续执行写内存的操作。



所以。在多CPU系统里,只有加LOCK了,才能保证其一定是原子性的。



象存/取内存的指令前,应加上:  lock movl %eax, a





然而,在单CPU系统中,一条指令我认为应该是原子性的。



 isjfk 回复于:2006-08-12 21:05:38


多 CPU 的时候也是跟多 CPU 的架构有关系。我记得早期的 486 多处理器,如果一个 CPU 执行的指令需要访问内存,则 CPU 会在这条指令的始终周期内始终占用内存总线。



 mik 回复于:2006-08-12 21:09:48


引用:原帖由 isjfk 于 2006-8-12 21:05 发表

多 CPU 的时候也是跟多 CPU 的架构有关系。我记得早期的 486 多处理器,如果一个 CPU 执行的指令需要访问内存,则 CPU 会在这条指令的始终周期内始终占用内存总线。 







呵呵~~ 若你没记错的话,那就更能说明为什么要在多CPU系统中锁总线了。



写内存指令是要超过一个周期滴



 JohnBull 回复于:2006-08-12 22:53:34


还没争完????

其实就一句话:

CPU不会在一条指令执行期间响应任何中断,其余自己想想就完了。



 JohnBull 回复于:2006-08-12 23:10:13


引用:原帖由 mik 于 2006-8-12 21:09 发表

呵呵~~ 若你没记错的话,那就更能说明为什么要在多CPU系统中锁总线了。

 





锁总线的性能是不能接受的!

现在的基于共享存储的UMA SMP设计,都是使用CACHE来回避总线占用,并采用特定手段解决缓存一致性的问题。



实际上这个帖子的主题根本牵扯不到这个问题。



 ~~~~ 回复于:2006-08-13 07:15:42


mik 想歪了,事实不是你想得那么回事。



 ~~~~ 回复于:2006-08-13 07:16:30


引用:原帖由 JohnBull 于 2006-8-12 23:10 发表



实际上这个帖子的主题根本牵扯不到这个问题。 





挖深点也好啊,让我这样的菜菜多学点:D



 dongpy 回复于:2006-08-13 12:48:24


支持whyglinux的观点~



int赋值操作,不管被分解成多少个汇编指令,只要保证仅有一条指令,完成对目标操作数(即多个线程共享那个变量)的访问,那么这个赋值过程就是原子的,因为那个写操作数的指令是不会被打断的。  



赋值操作中的其余指令只不过是一些辅助操作,比如从局部变量MOV到寄存器,这些操作被中断不会影响整个操作的原子性(因为有现场保护)。



 Alligator27 回复于:2006-08-13 20:53:23


我觉得一个操作是否原子, 要看该操作是否需要多次使用数据总线. 而不是几条指令.



大多数int赋值(立即数)只要一次memory cycle. 是原子的. 另外象 a=b 或 a++, 也许只要一条指令, 但必需读写两个memory cycle, 不是原子的, 需要加锁.



现在的CPU向多核发展, 同步是很重要的. 但code不能依赖某些硬件的实现, 先要保证正确.



 思一克 回复于:2006-08-14 08:57:42


在INTELX86 LINUX 单CPU下(根据LINUX atomic.h代码可以推断在许多其它平台也一样),

应用程序的全局int的读写(无论多thread,还是多PROCESS的shmem)都是原子的。不需要LOCK,放心地使用吧。



此时,C变为一条还是多条汇编无关,原因前面说过了。



多SMP下最好有人帮实验一下。



 思一克 回复于:2006-08-14 09:23:32


这个程序哪位在SMP LINUX机器上帮运行一下。



gcc -lpthread atomic.c -o testa

./testa      没有参数就是用原子复制

./testa X   有任何参数就是故意不用原子复制



如果不是原子的,程序会退出,否则一直跑。还可以观察到count被其它THREAD改了的情况





[CODE]

# include <stdio.h>

# include <stdlib.h>

# include <pthread.h>

# include <unistd.h>



int not_atomic = 0;

int runFlag = 1;

int count = 0;



void *testa( int id );



int main( int argc, char **argv )

{

    pthread_t thread;

    int i, ret;



    if(argc > 1 && argv[0][0]) not_atomic = 1;



    for( i = 0; i < 8; i++ ){

        ret = pthread_create( &thread, NULL, (void *(*)(void *))testa, (void *)i );

        if ( ret != 0 ){

            perror( "pthread_create" );

            exit(-1);

        }

    }

    while(runFlag) sleep(5);

    return 0;

}





void *testa( int id )

{

    int i;

    unsigned int k, v;



    k = 0x11111111*id;

    while(runFlag){

        for( i=0; i<10000; i++ ){

           if(not_atomic == 0) {

                count = k;

           } else {

                ((short*)&count)[0] = ((short*)&k)[0];

                ((short*)&count)[1] = ((short*)&k)[1];

           }

           if(count != k)

                printf("%08x %08x\n", k, count);



           v = count;

           if((v & 0x0000ffff) != (v >> 16)) {

                printf("NOT ATOMIC VALUE FOUND: %p %p\n", v, count);

                runFlag = 0;

           }

        }

    }

    return NULL;

}



[/CODE]



 思一克 回复于:2006-08-14 11:46:38


我找到SMP(2个CPU),运行结果也是和单个CPU一样



 kk_00 回复于:2006-08-15 11:26:16


楼主的问题是不是可以改成:

对于各种CPU来说,中断请求的优先级以及**是否**允许中断的条件判断都是怎么样的. 另外该CPU对延时处理都采用什么方式?



 isjfk 回复于:2006-08-15 11:31:30


晕,还没争完?:em06:



 oyscal 回复于:2006-08-20 22:13:59


如果int a=0;两个线程同时执行一次a++,结果不一定是2,很有可能是1.情况可能会是这样的:

1)线程1将a的值从内存读到寄存器

2)线程1在寄存器里将a++(a=1)

3)线程2此时被调度,将a的值从内存(注意此时内存里a=0)读到寄存器

4)线程2在寄存器里a++(a=1)

5)线程1被调度,将a写回内存,a=1

6)线程2被调度,也将a写回内存,a=1



最后a=1,over!



当然这个跟平台有关,但起码intel 8086体系的cpu是这样处理的



 whyglinux 回复于:2006-08-20 22:59:24


引用:原帖由 oyscal 于 2006-8-20 22:13 发表

如果int a=0;两个线程同时执行一次a++,结果不一定是2,很有可能是1.情况可能会是这样的:

1)线程1将a的值从内存读到寄存器

2)线程1在寄存器里将a++(a=1)

3)线程2此时被调度,将a的值从内存(注意此时内 ... 





赋值和自加还是有区别的:赋值是一个读-写的过程,而自加是一个读-改-写的过程,比起赋值来多了一个“改”的环节。



而且你上面说的 a++ 出现的问题只会出现在多CPU结构上,对于单CPU来说应该不存在这个问题。



 思一克 回复于:2006-08-21 08:43:49


to oyscal,



global_var++被翻译成为INC GV, 和赋值不是一个语句。赋值是原子的,在SMP多CPU中也是原子的。INC在SMP中不是原子的。你说的是正确的,但忽略了关键

[ 本帖最后由 思一克 于 2006-8-21 08:44 编辑 ]
顶端 Posted: 2006-10-01 13:31 | [楼 主]
未来的未来



性别: 帅哥 状态: 该用户目前不在线
等级: 品行端正
家族: 菠韬汹勇
发贴: 418
威望: 0
浮云: 1422
在线等级:
注册时间: 2006-01-11
最后登陆: 2009-08-02

5come5帮你背单词 [ lamp /læmp/ n. 灯 ]


弱弱问哈 void *(*)(void *) 什么意思,好费解哦!
顶端 Posted: 2006-10-01 17:31 | [1 楼]
lvdou





性别: 帅哥 状态: 该用户目前不在线
等级: 希望之光
家族: YD一族
发贴: 1930
威望: 0
浮云: 1181
在线等级:
注册时间: 2006-04-01
最后登陆: 2008-06-21

5come5帮你背单词 [ civilisation // n. 文明,文化,开化,教化 ]


这个帖子太强了,9成以上的都看不懂,那个社区的贴?
顶端 Posted: 2006-10-03 11:07 | [2 楼]
朱颜华发



性别: 保密 状态: 该用户目前不在线
等级: 鹤立鸡群
发贴: 1211
威望: 0
浮云: 1105
在线等级:
注册时间: 2006-04-16
最后登陆: 2007-08-07

5come5帮你背单词 [ challenge /'tælənd3ə/ vt. 向…挑战;n. 挑战 ]


Quote:
引用第1楼未来的未来于2006-10-01 17:31发表的:
弱弱问哈 void *(*)(void *) 什么意思,好费解哦!

怎么又是问这个啊
顶端 Posted: 2006-10-03 17:41 | [3 楼]
我来我网·5come5 Forum » 程序员之家

Total 0.027561(s) query 8, Time now is:05-15 23:28, Gzip enabled
Powered by PHPWind v5.3, Localized by 5come5 Tech Team, 黔ICP备16009856号