【问题标题】:working of fwrite in c++c++中fwrite的工作
【发布时间】:2012-12-22 01:07:34
【问题描述】:

我正在尝试在写入文件时模拟竞争条件。这就是我正在做的。

  1. 在 process1 中以追加模式打开 a.txt
  2. 在 process1 中编写“hello world”
  3. 在 process1 中打印 ftell,即 11
  4. 让 process1 进入睡眠状态
  5. 在process2中以追加模式再次打开a.txt
  6. 在进程 2 中写入“hello world”(正确附加到文件末尾)
  7. 在 process2 中打印 ftell 为 22(正确)
  8. 在进程 2 中写入“再见世界”(正确附加到文件末尾)。
  9. process2 退出
  10. process1 恢复并打印其 ftell 值,即 11。
  11. process1 写“再见世界” --- 我假设 process1 的 ftell 是 11,这应该覆盖文件。

但是,process1 的写操作是写到文件末尾,进程之间没有写争用。

我使用 fopen 作为fopen("./a.txt", "a+)

谁能告诉我为什么会出现这种行为以及如何在写入文件时模拟竞争条件?

process1的代码:

#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
#include "time.h"
using namespace std;
int main()
{

    FILE *f1= fopen("./a.txt","a+");
    cout<<"opened file1"<<endl;
    string data ("hello world");
    fwrite(data.c_str(), sizeof(char), data.size(),  f1);
    fflush(f1);
    cout<<"file1 tell "<<ftell(f1)<<endl;
    cout<<"wrote file1"<<endl;
    sleep(3);
    string data1 ("bye world");;
    cout<<"wrote file1 end"<<endl;
    cout<<"file1 2nd tell "<<ftell(f1)<<endl;
    fwrite(data1.c_str(), sizeof(char),  data1.size(),  f1);
    cout<<"file1 2nd tell "<<ftell(f1)<<endl;
    fflush(f1);
    return 0;
}

在process2中,我已经注释掉了sleep语句。

我正在使用以下脚本运行:

./process1 &
sleep 2
./process2 &

感谢您的宝贵时间。

【问题讨论】:

  • 这不是竞争条件。他们按顺序写。
  • 我假设当 process1 唤醒时 ftell 为 11,process1 的文件指针 f1 将尝试从那里写入。如何模拟竞争条件,一个进程覆盖另一个进程的内容?谢谢。
  • ftell 可能指向读取缓冲区中的某个位置,但是当涉及到实际文件时 - 您已被序列化。
  • fwrite 如何知道从哪里开始写入数据?它不是存储在进程的文件指针中吗?如果是,则 process1 的 end_of_file 已过时(即 process2 已超出该点写入)。因此,当 process1 恢复写入时,我希望它会使用它的值并立即开始写入。 fwrite 会在写入之前重新计算 EOF 吗?
  • 操作系统会处理这个问题。 ftell 给你的结果可能是一个库函数错误(如果你愿意,你可以研究它),但是当你以追加方式打开文件时,它会追加。

标签: c++ fwrite ftell


【解决方案1】:

作者代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BLOCKSIZE 1000000

int main(int argc, char **argv)
{
    FILE *f = fopen("a.txt", "a+");
    char *block = malloc(BLOCKSIZE);

    if (argc < 2)
    {
    fprintf(stderr, "need argument\n");
    }
    memset(block, argv[1][0], BLOCKSIZE);
    for(int i = 0; i < 3000; i++)
    {
    fwrite(block, sizeof(char), BLOCKSIZE, f);
    }
    fclose(f);
}

阅读器功能:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BLOCKSIZE 1000000

int main(int argc, char **argv)
{
    FILE *f = fopen("a.txt", "r");
    int c;
    int oldc = 0;
    int rl = 0;

    while((c = fgetc(f)) != EOF)
    {
    if (c != oldc)
    {
        if (rl)
        {
        printf("Got %d of %c\n", rl, oldc);
        }
        oldc = c;
        rl = 0;
    }
    rl++;
    }

    fclose(f);
}

我跑了./writefile A &amp; ./writefile B 然后./readfile

我知道了:

Got 1000999424 of A
Got 999424 of B
Got 999424 of A
Got 4096 of B
Got 4096 of A
Got 995328 of B
Got 995328 of A
Got 4096 of B
Got 4096 of A
Got 995328 of B
Got 995328 of A
Got 4096 of B
Got 4096 of A
Got 995328 of B
Got 995328 of A
Got 4096 of B
Got 4096 of A
Got 995328 of B
Got 995328 of A
Got 4096 of B
Got 4096 of A
Got 995328 of B
Got 995328 of A

如您所见,A 和 B 的长度很长,但它们并不完全是 1000000 个字符长,这是我写的大小。整个文件,在第一次运行较小的试运行后,仅不足 7GB。

供参考:Fedora Core 16,我自己编译的 3.7rc5 内核,gcc 4.6.3,x86-64 和 lvm 之上的 ext4,AMD PhenomII 四核处理器,16GB 内存

【讨论】:

  • fflush 似乎没有帮助。
  • 在您的阅读器代码中,您可能希望打印为未打印的循环的最后一次迭代计算的值。否则, if 将不会报告文件中最后 N 个相同的字符。我在 while 循环结束后添加了您的 printf 行,它报告了文件中的所有字符。
  • 谢谢比尔。在最后一次迭代之前它确实出错了,所以这不是我关心的事情。这不像我声称的“这是完美的代码”——事实上它缺少很多错误处理......
【解决方案2】:

以追加模式写入是一种原子操作。这就是它不会损坏的原因。

现在...如何破解它?

尝试内存映射文件并从两个进程写入内存。我很确定这会破坏它。

【讨论】:

  • 你的意思是写文件不会有竞争条件,如果你从不同的进程以追加模式打开文件?不过这很难相信。
  • SO 上的这个线程似乎表明它不是原子的,除非在特定情况下(并且仅在 Linux/Unix 上,而不是 Windows 上):stackoverflow.com/questions/7552451/…
  • 大多数 Unix 和 Linux 文件系统都支持原子追加。没有它写入日志文件会很有趣。众所周知,NFS 尤其不支持它们。而且没有通用的方法可以确定您是否正在处理原子附加支持。
【解决方案3】:

我很确定你不能依赖这种行为,但它可能在某些系统上可靠地工作。如果您“足够努力”,从两个不同的进程写入同一文件可能迟早会导致问题。和草皮定律说,这正是你的老板检查软件是否正常工作的时候,你的客户接收你出售的系统的时候,或者你完成需要很长时间才能生成的报告的时候,或者其他一些重要的时间。

【讨论】:

  • 我试图打破这个东西,即让它们互相覆盖。不过谢谢你的笑声。
  • 尝试写入更大的块(可能是几兆字节),并真正敲出数据。您可以通过在一个进程中写入大块“A”和在另一个进程中写入大块“b”来简化操作。哦,不要使用 fflush!
  • 听起来是个挑战... ;)
  • 好吧,我把它弄坏了……我在一个循环中写了 1000000 个字符的块,然后写了一点代码来分析输出: Got 1000999424 of A Got 999424 of B Got 999424 A 得到 4096 B 得到 4096 A 得到 995328 B 得到 995328 A 得到 4096 B 得到 4096 A 得到 995328 B 得到 A 995328 B 得到 4096 A 得到 4096 B 得到 995328 B 得到 995328得到 4096 of B 这是我的分析器程序的输出。它显然不起作用。
  • 如果您不介意,我会添加另一个答案,代码显示它已损坏...
【解决方案4】:

您尝试破坏或查看的行为取决于您正在使用的操作系统,因为写入文件是系统调用。 根据您告诉我们的第一个文件描述符不会覆盖第二个进程写入的内容,您在两个进程中以附加模式打开文件的事实可能在实际写入之前实现了 ftell 值。

您是否尝试对标准的 open 和 write 函数做同样的事情?可能也很有趣。

编辑:C++ 参考文档解释了 fopen 附加选项here: “追加/更新:打开一个文件进行更新(输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek,fsetpos,rewind)影响下一个输入操作,但输出操作移动位置回到文件末尾。” 这解释了您观察到的行为。

【讨论】:

  • 在process1进入睡眠状态之前,它的ftell是11。当它醒来时,它的值仍然是11。然而,与此同时,process2已经写入了。所以,我希望 process1 能从 11 点恢复写入,因为那是它睡觉前的 EOF。如果每个 fwrite 都重新计算 EOF(就像使用 O_APPEND 标志打开一样),那么它会解释结果。
  • 您可以通过在文件中不写入任何字符,使用fwrite("", sizeof(char), 0, f1),然后再次检查 f1 的 ftell 值,轻松尝试查看是否是这种情况。
  • 我在 C++ 参考文档中找到了一些有用的东西:“附加/更新:打开一个文件进行更新(包括输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek,fsetpos,rewind)影响下一个输入操作,但输出操作将位置移回文件末尾。如果文件不存在,则创建该文件。 cplusplus.com/reference/cstdio/fopen 我认为这解释了第一个进程将在文件末尾写入之前发生的任何事情的事实。我将编辑我的答案以添加这些详细信息。
  • 那么您如何解释 Mats Petersson 给出的以下代码所观察到的行为。好像是两个进程的fwrite混在一起了。
  • 很难说他的代码究竟是如何工作的,因为他正在开发自己的基于 Fedora 发行版的内核,所以他可能已经修改了系统调用的工作方式。但是他似乎没有检查 fwrite 的返回值,这可以解释这里的行为,因为 fwrite 返回它在流中成功写入的字符数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-22
  • 2016-03-14
相关资源
最近更新 更多