为什么 一个线程读数据,一个线程写数据,要加锁?

zrdongjiao 2012-10-18 09:54:50
为什么 一个线程读数据,一个线程写数据,要加锁?


这种奇怪的言论,不知道为什么会出现?



随手举个例子



全局变量int g_val;



线程1


printf("%d",g_val);




线程2

g_val++;




显然不需要 加锁啊。 多个线程修改数据,那么则需要加锁,如果只是一个读,一个写,加锁作甚?




问题2:

典型的读写者模型: 多个线程读,多个线程写。


http://blog.csdn.net/morewindows/article/details/7596034问中提到的这个模型

与上一篇《秒杀多线程第十篇 生产者消费者问题》的生产者消费者问题一样,读者写者也是一个非常著名的同步问题。读者写者问题描述非常简单,

有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。


问:为什么要如此规定。读的时候不能写,写怎么了?影响数据? 还是怎地?写的时候,不能读?读有如何。

这种规定的模型真不知道是如何提出来的。



...全文
10122 51 打赏 收藏 转发到动态 举报
写回复
用AI写文章
51 条回复
切换为时间正序
请发表友善的回复…
发表回复
蓝贝壳壳 2014-08-20
  • 打赏
  • 举报
回复
引用 7 楼 zrdongjiao 的回复:
是5 线程1 可能读取到的数据是4,5,也可能不是4,不是5. 作为一个全局变量 ,我们其赋值为1,线程1很有可能是1 好了,我再做一个变化: 版本2: 全局变量int g_val; 线程1 { while(1) { printf("%d\n",g_val); Sleep(50); } } 线程2 { while(1) { g_val++; Sleep(50); } } 你觉得还需要加锁吗? 我觉得不需要了。 [Quote=引用 5 楼 的回复:] 假设线程2 { g_val=4; g_val++; } 如果不加锁,你知道g_val++后g_val是多少吗? [/Quote]
试想一个指针P 当前是FFFFFFFF B线程要把它写成EEEEEEEE 如果P正好不是4字节对齐的 CPU需要两个指令读取P 而恰好在读第二个字的时候读线程被中断 等读线程再次唤醒后 A线程读到的将是EEEEFFF 显然不是一个合法地址 这个是你能接受的吗?
yujie_v 2012-10-29
  • 打赏
  • 举报
回复
一个也属于多个的范围,lz概念弄混了。
只有原子操作不需要加锁的。
就呆在云上 2012-10-27
  • 打赏
  • 举报
回复
[Quote=引用 32 楼 的回复:]
30楼正解,不加锁可能导致脏数据,但是如果你认为暂时的脏数据是可以接受的,那么不加也行。
[/Quote]
不仅如此,人们觉得需要锁,更多是因为程序崩溃吧。
就呆在云上 2012-10-27
  • 打赏
  • 举报
回复
那你可以不加,如果出问题,也极小概率。
估计你能接受。
如果你的想法正确,为何还要读写锁这玩意?依你之见有写锁世界就ok了嘛。
gongdath 2012-10-26
  • 打赏
  • 举报
回复
[Quote=引用 41 楼 的回复:]

引用 18 楼 的回复:

多线程同步我的常用技巧(原则就是保证只一个线程写):
比如线程1和线程2通信,线程1发命令让线程2结束,线程2给出回应(在结束之后),那么:
线程1:
while(0 == order) {}; //线程做事
order = 2; //线程1结束

线程2:
order = 1; //命令线程1结束
while(2 != order){}; //……
[/Quote]

我很长时间不用c和c++了,但是上面这段代码对于JAVA来说就是错误的,因为没有考虑JDK对程序的优化,所以线程2有可能永远看不到线程1的结束,因为线程2可能用的是缓存的order的值。
gongdath 2012-10-26
  • 打赏
  • 举报
回复
有些人认为读取不到最新值是可以忍受的,反正下次就可以读到了,其实在程序优化的时候,程序可能永远也读不到最新的值,因为读到的缓存的值。(我记不清C是否这样优化了,但是JAVA肯定是这样优化的。)
skyworth98 2012-10-26
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]
对于一个线程写,多个线程读,我从来不加锁。
对于写,哪怕就如下:
g_i = 1;
g_i++;

也不会出问题,无非就是可能读到中间值,这要看你的业务了,如果不能接收中间值,那就有问题,但这是你的业务的问题;

如果写是这样:
g_i = 1;

则肯定不会出问题,对于读的一方,要么读到g_i赋值前的值,到么得到新值1,不存在中间结果;


当然,共享值的类型要设计好,……
[/Quote]
...
Asherandy 2012-10-26
  • 打赏
  • 举报
回复
同理、
假设 现在我们 取 i 的值
你读的时候 i = 1
同时我给i写入 2
假设 我们就是在 同一个时间 进行读 写
你让程序 给你返回什么?
1? 1.5? 2 ?
Asherandy 2012-10-26
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 的回复:]
你没有回答为什么不正确吧
引用 1 楼 的回复:
当然要加锁了, 否则在写时去读, 可能不正确.
[/Quote]


现在我有 int i=0;
你读取了、可就在你读到的那一刻的后 0.0000000000......1 秒 我给 i 写入数据 2
这个时候 你看见的 还是 1
可实际 i 的值已经是 2 了
懂?
wangweiyan2007 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 的回复:]

多线程同步我的常用技巧(原则就是保证只一个线程写):
比如线程1和线程2通信,线程1发命令让线程2结束,线程2给出回应(在结束之后),那么:
线程1:
while(0 == order) {}; //线程做事
order = 2; //线程1结束

线程2:
order = 1; //命令线程1结束
while(2 != order){}; //等待
//线程1结束

实在……
[/Quote]

两个忙等待,你知道CPU上升的峰值么,不用考虑效率么?
wangweiyan2007 2012-10-24
  • 打赏
  • 举报
回复
g_val++不是原子操作,转换成汇编的话好像是 mov g_val EFC; INF EFC; mov EFC g_val;三句汇编代码(可能写错了,汗),而且程序编译的时候编译器会将代码重新排列,所以如果没有锁的话,打印g_val的值可能在这三条汇编语句当中执行。所以要有临界区。
tigershi2000 2012-10-24
  • 打赏
  • 举报
回复 1
“问:为什么要如此规定。读的时候不能写,写怎么了?”
这种问题真不知道你是怎么问出来的。
那问你,你去厕所的时候吃不吃饭啊?
从理论上讲,吃饭没问题,当年KFC有免费活动的时候,很多人就在厕所里吃,没问题,一点问题都没有,绝对吃不死人。但正常的情况下,有人吃没有?
就算暂时程序不出问题,你这样写,能保证以后都一直安全吗?最简单的例子,你读写一个int当然不出错,但网页显示图片的时候,图片比较大,是分开显示的。你读的时候同时写,用户看到的是个什么玩艺。
GibGas 2012-10-24
  • 打赏
  • 举报
回复
你这个例子是正确的。你不能保证读写都是原子操作,就要加锁。
再比如数据库操作,用户有时需要知道系统中某一个值的最新值,然后进行其他操作,即使只有一读一写两个线程,还是要阻塞写线程来读取。比如,要得到一组十个数据来进行计算,如果写线程在读取过程中单一修改其中一个数值,可能会使得结果不对。

这个要具体情况具体分析,就好比其实主楼举的例子是可以不加锁的,但是也是没有任何意义的例子。一般情况来讲建议多线程加锁是为了避免不必要的问题。

[Quote=引用 19 楼 的回复:]

看了baichi4141的回复,补充一下:


如果修改数据,值修改了一部分,比如一个double,很可能数据 既不是 修改前,也不是预期修改后的数据了。


我在其基础上补充:

如:修改一个链表, 写线程每次修改10个节点, 10个节点并非一条指令能够完成的。


多条指令才能搞定。 这种情况下, 更是随即的,添加节点,你懂的,

往前面插入节点,每次插入节点,就……
[/Quote]
Analyst 2012-10-23
  • 打赏
  • 举报
回复 1
如果只更改一个变量的话是没有问题的,只要加上volatile修饰。
但是如果你改了多个变量,就会读到部分更新的数据,这种情况下不加锁是不正确的。
wdongnian 2012-10-22
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 的回复:]

大家可以看看boost的atomic_read32函数的实现,对于读,任何时候都不用加锁的,对于写,如果多线程写,而又不能用一条汇编完成的,可能要加锁。
而你的情景下,就一个线程写,所以完全不用加锁,此时如果不能用一条汇编完成写,则分两种情况:
一:你一次写了几次,但每次写都能一条汇编完成;
二:你一次只写一次,但不能一条汇编完成;

对于第一种情况,我认为也不用加锁,就算读到中间数据……
[/Quote]

我看是你没有理解吧 还在这里吵来抄去的 锁是不能保证顺序执行 但是锁可以保证互斥执行 执行顺序会有现代处理器或者编译器进行优化 当然也可以使用barrie来防止执行顺序优化 如果一个线程读取了修改前的值 并对它做了修改 这样是不是把第一个线程的修改才做覆盖了!
bsnry 2012-10-19
  • 打赏
  • 举报
回复
多核呢?

单挑指令搞定的,也不要加锁?

25L





[Quote=引用 30 楼 的回复:]

加不加锁,要看你是否需要保证你写线程中所有对共享变量的操作是否需要原子化而定。如果你不需要,就是这个操作的中间值你也可以接受,那么就加不加无所谓了。
[/Quote]
breaksoftware 2012-10-19
  • 打赏
  • 举报
回复
加不加锁,要看你是否需要保证你写线程中所有对共享变量的操作是否需要原子化而定。如果你不需要,就是这个操作的中间值你也可以接受,那么就加不加无所谓了。
youngwolf 2012-10-19
  • 打赏
  • 举报
回复
锁不是用来保证执行顺序的,建议先去打听一下再出来。
horris 2012-10-19
  • 打赏
  • 举报
回复
继续楼上的,似乎有这么一个线程亲和性的属性,它限定了某个线程只能在指定的CPU(核)上运行。如此一来多个线程实际上不是“同时的”,而是“分时的”了,当然CPU很快,用户看不出来,另外CPU很多时间是在闲着,所以“分时的”多线程也能大大提高总体执行效率。以前在单核CPU上的操作系统,就是这种“分时”多线程。
当然了,“同时的”比“分时的”更能充分利用计算机的处理能力,更快更强更好,但也要看操作系统是否支持。不过我对这些细节只是知道个皮毛,没有时间和精力去搞清楚细节。
总之我们写程序的,不必太关心底层软硬件的细节,要想挖掘计算机能力,写多线程程序的,还是每逢多线程读和写共享变量,必写同步保护,同时在设计上避免死锁,这样就能保证写出健壮的、高效的、适应性强的程序。
horris 2012-10-19
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 的回复:]
有介绍多核的书吗

现在的用户基本都是多核的, 如你所说,我们的代码 得考虑多核了。

所以:无论是 一条指令搞定的修改数据,也得考虑同步保护了。
[/Quote]
多线程方面的知识我也知道的零散,如何你用微软的开发工具,可以看MSDN,里面有多线程的章节。比如InterlockedIncrement这个Win32 API的帮助里,可以看到一些知识和链接。
另外,似乎每个线程都有一个CPU(核)的“亲和性”这样一个属性。你可以将你的进程里的所有线程的“亲和性”都设为同一个CPU(核),这样对于原子操作,就不必加同步保护了。楼上说的linux内核没加同步保护可能就是这种情况。
我对线程亲和性的了解不多,只是知道有这个事情,你可以查查MSDN了解更详细的情况。
加载更多回复(30)

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧