免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 34919 | 回复: 84
打印 上一主题 下一主题

关于多线程对同一个文件进行写操作,记得坛子里面讨论过,不需要互斥,原因什么来着? [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-10-18 13:46 |只看该作者 |正序浏览
记不清楚了,也没有找到,请达人释疑。

论坛徽章:
0
85 [报告]
发表于 2010-10-30 14:00 |只看该作者
回复 84# epegasus


乱了就是:2个进程写同一文件同一位置,一个写全“0”, 一个写全“1”。
如果不乱,读到的结果要么是00000000,要么是11111111. 不可能是000111111或1111111000
乱了就出现混合了。

论坛徽章:
0
84 [报告]
发表于 2010-10-30 01:20 |只看该作者
回复 83# 思一克


    先把什么叫"乱"讲清楚.以后就直接针对普通文件讨论
之前说的乱都是指2个写的交错执行. 导致"最终文件可读结果" 是2次或多次写结果的交错
定义一个交错就是一次write调用写入的2个部分被另一次write写入打断. 这里说的写入都是指对文件的最终结果产生影响,能由一次最终读看到.
当然首先就假设了这次最终读是不能被新的写入打断的.如果打断了将不会看到上面说的最终写的结果.这个与read加锁不加锁没关系.

再看read和write发生多进程操作的问题
首先一次read肯定读到的是他即将想要读的那部分的最终结果,这个最终结果至少存在于他开始读的那一时刻.而他读的时候至少有个原子区域是禁止修改的(按页的操作必定在使用一个页的时候锁定这个页)
当多个写发生时,而且都加了append方式.则read读的内容也是这多个写的最终结果,因为没有写会去修改别人写过的部分.也就是读永远发生在所有的写之后.而非append方式因为对于某个页,读可能发生在某个写前.而其他页则发生在这个写之后所以就导致读的的内容有2个最终可读结果的交错.这完全可理解为读写不同步-不能保证读总是在写后的原因导致的,本质上和多个write是否交错没什么关系

论坛徽章:
0
83 [报告]
发表于 2010-10-29 23:39 |只看该作者
回复 81# epegasus


   
    2.4 2.6 pipe read都是加锁的.混乱原因如上面分析.代码和标准中都得到确认.
不过你这确实引如一个新问题.在普通文件中的read可能出现的和write同步的问题.但问题只发生在并行执行write read
不能解释所有write执行完后 再read是否发生混乱
---------
是的。
一个进程写,一个读,不会乱的。
2个以上在写,一个在读就乱了。因为读不考虑写锁i_mutex的状态的。
这个设计是合理的。因为多数时候是大量的进程同时读一个文件按(比如配置文件),而写的机会很少。
所以读不能在内核加锁头。如果有必要,用户自己加。

pipe的读写在块大小小于PIPE_BUF时候是无需锁的。绝对不会乱。这个有准确的说明,并且有很多精彩的程序就根据这个设计的。

论坛徽章:
0
82 [报告]
发表于 2010-10-29 20:49 |只看该作者
感觉还是使用互斥锁方便

论坛徽章:
0
81 [报告]
发表于 2010-10-29 17:48 |只看该作者
回复 80# 思一克


    2.4 2.6 pipe read都是加锁的.混乱原因如上面分析.代码和标准中都得到确认.
不过你这确实引如一个新问题.在普通文件中的read可能出现的和write同步的问题.但问题只发生在并行执行write read
不能解释所有write执行完后 再read是否发生混乱

论坛徽章:
0
80 [报告]
发表于 2010-10-29 16:17 |只看该作者
TO epegasus,

问题我基本上知道了。
那个写的锁头保证了写同一个文件的顺序性,一个完成了下一个再写。
但无法保证读写的原子性。
因为读文件sys_read没有什么锁。
所以,读的线程可以读到一个刚写了一半的数据。结果就造成了那乱的结果。

这个好比4个进程赋值一个INT, 一个进程读这个INT.
如果INT的赋值不是一条指令的,虽然有写锁控制赋值的顺序性(一个赋值完成了下一个才来),但读进程读到的数据可以是乱的,因为读进程不管那么个锁的状态。

论坛徽章:
0
79 [报告]
发表于 2010-10-29 13:14 |只看该作者
回复 77# 思一克


    这个问题结论差不多是pipe和普通磁盘文件有时可能必须加锁.
先说说之前对77楼这段代码理解有错误.
2.6中上面普通文件一般先调用 fs\read_write.c : do_sync_write
  1.                  for (;;) {
  2.                 ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
  3.                 if (ret != -EIOCBRETRY)
  4.                         break;
  5.                 wait_on_retry_sync_kiocb(&kiocb);
  6.         }
复制代码
其中aio_write中一般是通过 mm\filemap.c : generic_file_aio_write完成真正的写入
  1.         mutex_lock(&inode-

  2. >i_mutex);
  3.         ret = __generic_file_aio_write_nolock(iocb, iov, nr_segs,
  4.                         &iocb->ki_pos);
  5.         mutex_unlock(&inode->i_mutex);
复制代码
这个函数中的这个代码保证了一个原子性,但是通过第一段代码可想而知道一个不完全的

write导致整个write变成了非原子的.并且结合O_APPEND的方式就形成了交错写.但是数据不会因覆盖而丢失.

然后是基本结论和前一个精华贴子一楼的差不多
http://bbs2.chinaunix.net/viewth ... p;extra=&page=1
关于pipe已经有结论.POSIX标准也是这样描述,写入数据小于等于PIPE_BUF保证写入是原子的.否则会出现交错.对于O_NONBLOCK 方式.这种原子

性的含义是要么都写进去.要么一个都写不进去.不是这样方式倒没有严格定义.即有可能写一部分而返回.

关于4K是否就是PIPE_BUF?是的.但是不能假定从最开始写的4K里面是原子.而是保证在每次写时传入的参数. 因为pipe在内存中的数据的方式,

不同于普通文件,普通文件是静态的文件地址偏移空间,可以用确定的文件页.所以既不能按总字节数去确定边界也不能用pipe中数据长算边界.

2.4的pipe只有一个页的内存.所以其按其规则一个大于4K的write必定不能一次执行完.必然写入4K后返回.O_NONBLOCK方式允许交叉.并在等待

前释放了SEM旗语,因而交错

2.6有16个页,一次可以写多个页,只要总容量允许.如果容量不够也会阻塞并释放旗语从而导致交错


然后是74楼提到的问题不影响一次__generic_file_aio_write_nolock的原子性. 理由是write是针对内存中的文件映像操作.从读或写的层面考

虑不管文件内容在不在磁盘中都必须先在内存中对应的文件的中的页.然后是对这个页的写.一但写入完成则对其他read write都是可见的.所以

一致性在这里已经得到保障.不过从mutex_unlock(&inode->i_mutex); 这段代码下面的几行可以看出O_SYNC的同步并不受保护.也就是一次原子

性的写入和写得结果存入磁盘不是原子的.其实也不必要保证,反正同步到磁盘的是较新的文件映像就是了.绝不会用旧的覆盖新的.
在加上O_APPEND后的写由以上分析不会产生覆盖和文件空洞.因为2个真实的原子的写文件印象绝不可能同时操作,即使他们交错执行了.
但是对于上面的返回-EIOCBRETRY的错误非常少见。目前还没确定具体是什么地方发生。而返回非-EIOCBRETRY的时候多半就是内存耗尽反而不用加锁了因为只执行一次原子写。
一个普通磁盘文件write总是可以阻塞的,而这个阻塞发生在fs\buffer.c中 也就是发生在写文件的未调入内存的部分时从磁盘获取内容的时候发生。但是这个阻塞不会破坏上面的原子性。

论坛徽章:
0
78 [报告]
发表于 2010-10-22 16:19 |只看该作者
本帖最后由 epegasus 于 2010-10-22 16:32 编辑

回复 77# 思一克


    这个不能完全确定.目前还在探索中...

这个问题很早就提出来了。真的有必要把这个牛皮癣问题彻底讨论清楚。
或许一个搞懂文件系统的人已经知道原因了。只是没说出来。

这里已经有2个关于这个问题的精华帖子:
http://bbs.chinaunix.net/thread-804742-1-2.html
http://bbs2.chinaunix.net/viewth ... p;extra=&page=1


75楼的提法有道理.
简单说就是读写函数不是同步IO的(这里的IO指文件IO,而不是磁盘设备IO。这里的同步和异步系统调用是的2个领域的概念)
不过这里的能原子保证的可能不是扇区,而是特定文件系统下的逻辑块。
不过有待确定.

同时看看O_SYNC选项到底有没有真正互斥的效果

论坛徽章:
0
77 [报告]
发表于 2010-10-22 16:10 |只看该作者
回复 61# epegasus

epegasus 说的是对的。详细看了程序,是那样的。
generic_file_aio_write 被调用,它自己对inode上锁了,不完成write其它进程不能写同一个文件。

mutex_lock(&inode->i_mutex);
ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
mutex_unlock(&inode->i_mutex);

至于我们编程测试为什么有交错?估计是sys_read不加锁的缘故(?)。
可以进一步研究看。
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP