免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: rain_fish
打印 上一主题 下一主题

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

论坛徽章:
0
71 [报告]
发表于 2010-10-21 17:47 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
0
72 [报告]
发表于 2010-10-21 19:10 |只看该作者
回复 70# chenzhanyiczy


    这段到没注意到.
不过并不矛盾
恰恰是我(6;4)楼说的因为偏移导致的错乱 而不是因为并行交叉写
所以我上面好几个帖子提到测试方法都强调要2个进程写入通样长度的.
你2个不一样长.发生了错乱就无法分辨到底是什么引起的

论坛徽章:
0
73 [报告]
发表于 2010-10-21 19:14 |只看该作者
回复 68# epegasus

我对这个问题的细节也不是100%熟悉,所以是在讨论。
你的分析结论好像是不对的。

2个进程同时写同一个文件的同一位置也有交错发生,很容易编程实验出来。
还有,交错不是因为没有写完(write返回值不等于参数数值)。而是完全写完的时候发生的交错。
也就是说,write完全写完的时候,写不是原子的。

还有,pipefs是用inode锁的,并且是不可SEEK的(排除了SEEK错误引起的覆盖)。
pipe不会重叠,但多个进程写一个pipe在块大小超过PIPE_BUF时候不是原子的,数据交错的乱七八糟。但数据没有丢一个字节。

所以,你说写文件用inode->i_mutex互斥了,只有第一个write写完,才进行第二个write应该不正确。

请再详细研究看。

论坛徽章:
1
申猴
日期:2014-02-11 14:50:31
74 [报告]
发表于 2010-10-21 20:53 |只看该作者
回复  chenzhanyiczy


    这段到没注意到.
不过并不矛盾
恰恰是我(6;4)楼说的因为偏移导致的错乱 而 ...
epegasus 发表于 2010-10-21 19:10



这个跟长短没有关系,只要是同一个页块内就会发生交叉。

论坛徽章:
0
75 [报告]
发表于 2010-10-22 10:23 |只看该作者
回复 74# chenzhanyiczy
首先阐明两个概念:原子操作,可被打断(中断)和数据完整性(data integrity)。
不可被打断的操作一定是原子操作,但原子操作不一定非得不可被打断,而只是指不可被对同一数据资源的操作打断。
也就是说:原子操作只是说对本数据或资源的操作是原子的。而由其他进/线程执行的对其他数据或资源的操作是可以打断本操作的。
就像你用mutex锁定对一段数据的访问,但这并不会不被其他线程打断。
所以原子操作应该和一段数据/资源的访问相提才是有意义的。而数据完整性是指数据操作的结果和所期望的是一致的。
多线程环境下,原子操作可以保证数据完整性,否则不可以。

再来看read和write函数。所以问题就是:write函数本身是否可以保证多线程对同一文件写的数据完整性?
答案是NO。

因为内核对文件的读写事实上是按块(扇区)为单位进行的,一般每个扇区大小默认512字节,当然程序可调。
而内核的write实现并没有保证它是原子的,而只是保证它对单个扇区的读写是原子的,因为硬盘的读写就是以扇区为单位的。
这也提高了效率,否则如果write操作写的是两个不同的文件,那么如果write是原子的性能就很低。

所以只要你让多个线程同时写入一个以上字节的数据到同一文件,就可能导致数据的不完整。
假设两个线程t1,t2:
  1. write(fd, "ab");
  2. write(fd, "cd");
复制代码
那么a可能写入到扇区x,b写入到扇区x+1。
c写入到扇区x,d写入到扇区x+1。
这样写入顺序就有6种:
  1. a, b, c, d-------结果-------->cd
  2. a, c, b, d------结果--------->bd
  3. a, c, d, b------结果--------->db
  4. c, d, a, b------结果--------->ab
  5. c, a, d, b------结果--------->db
  6. c, a, b, d------结果--------->bd
复制代码
结果有4种:ab,db,bd,cd
这4种结果只有2种是数据完整的:ab和cd,假如你不关心写入顺序的话。其他都被写乱了。
所以数据完整性应该由调用者自己来保证。

论坛徽章:
0
76 [报告]
发表于 2010-10-22 10:31 |只看该作者
回复 75# qingfenghao

为什么实际会比较少产生数据不完整的情况?因为发生的概率比较低,多个线程对同一文件而且要对相同的一个以上的扇区同时写入,才有可能。为了确保这一点,
多个线程都要对文件的同一个position,而且要写入512个字节以上才可确保冲突存在。而冲突存在也不定会发生,这还和当时线程调度的具体情况有关。当然,写入的数据
越多,冲突的可能性越大。写入1024字节就比写入512字节发生冲突的可能性高。有兴趣可以写代码试试。

论坛徽章:
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不加锁的缘故(?)。
可以进一步研究看。

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

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

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

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP