【问题标题】:Write reliably to same file from different processes从不同进程可靠地写入同一文件
【发布时间】:2011-01-17 10:30:09
【问题描述】:

我确实创建了一个运行良好的小型 C++ 跟踪解决方案。在一个进程中一切都很好,但是当我从不同进程打开输出文件时,数据没有正确写入。我确实使用 FILE_SHARE_WRITE 打开了文件,以便能够在文件已经打开时写入文件。然后我确实创建了一个命名互斥锁以确保进程之间的正确同步。但似乎这还不够。根据 MSDN,这确实在一个进程内工作,但不在不同进程之间工作。 接下来我尝试在每次写入后调用 FlushFileBuffers,而互斥锁仍然被持有,但数据仍然像这样扭曲

格式是时间进程id/线程id方法进入/离开/严重性命名空间+方法,然后是消息文本。

10:29:42.994 7448/2236       }} Dll2.Test.fndll2 L1 -> Duration: 0.094s
10:29:43.040 7448/2236 {{       Dll2.DllMain L1
10:29:43.134 7448/2236 Info     Dll2.DllMain L1 Process detach
10:29:43.181 7448/2236       }} Dll2.DllMain L1 -> Duration: 0.141s
     }} Dll2.DllMain L1 -10:29:42.681 7448/2236 Info     Dll1.DllMain L1 Process attach
10:29:42.728 7448/2236       }} Dll1.DllMain L1 -10:29:42.744 2216/5510:29:42.775 7448/2236 {{       Dll1.Test.fndll1 10:210:29:42.822 7448/2236 Info     Dll1.Test.fndll1 10:29:42.837 2216/557610:29:42.853 7448/2236       }} Dll1.Test.fndll1 L110:29:42.884 2216/557610:29:43.306 7448/2236 {{       Dll1.DllMain L1
10:29:43.353 7448/2236 Info     Dll1.DllMain L1 Process detach
10:29:43.400 7448/2236       }} Dll1.DllMain L1 -> Duration: 0.094s

我看过FILE_FLAG_NO_BUFFERING,但它有严重的限制,似乎not easy to use

有没有人知道在不扭曲输出的情况下同步写入同一个文件的正确方法?

你的,

阿洛伊斯·克劳斯

【问题讨论】:

  • 是否可以将您的跟踪解决方案实现为 COM 服务器?

标签: c++ c windows winapi


【解决方案1】:

我终于让它工作了。诀窍是在非常写之前在文件末尾查找。否则我会覆盖大约一半的输出,尽管我在每次写入之前都会使用跨进程互斥锁进行锁定。

代码如下所示

__int64 Seek (HANDLE hf, __int64 distance, DWORD MoveMethod)  // from MSDN 
{
   LARGE_INTEGER li;
   li.QuadPart = distance;
   li.LowPart = SetFilePointer (hf, 
                                li.LowPart, 
                                &li.HighPart, 
                                MoveMethod);

   if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
   {
      li.QuadPart = -1;
   }

   return li.QuadPart;
}



void WriteToFile(TCHAR *pData)
{
    DWORD dwWritten = 0;

    if( FALSE == ::WriteFile(_hFile, pData, _tcslen(pData)*sizeof(TCHAR), &dwWritten, NULL) )
    {
        _LastError = ::GetLastError();
        ASSERT__(FALSE);
    }
}

virtual void Write(TCHAR *pStr)
{
    if( _hWriteMutex != NULL )
    {
        DWORD res = ::WaitForSingleObject(_hWriteMutex, 120*1000);
        if( res == WAIT_OBJECT_0 || res == WAIT_ABANDONED ) // another process might have crashed while holding the mutex
        {
            // Ensure that we are really writing at the end of the file 
            __int64 fPos = Seek(_hFile, 0, FILE_END);
            WriteToFile(pStr);
            ::ReleaseMutex(_hWriteMutex);
        }
        else
        {
            ASSERT__(FALSE);
        }
    }
    else
    {
        WriteToFile(pStr);
    }
}

【讨论】:

    【解决方案2】:

    SQLite 使用文件锁来确保从多个进程访问它的数据库文件时不会发生这种情况。您是否尝试过使用LockFile? (example)。过去,我使用 SQLite 数据库从多个进程进行日志记录,但在这种情况下,这可能有点太多了。

    【讨论】:

      【解决方案3】:

      我不知道“正确”的方式,但你正在做的事情在我看来已经是正确的了。我能想到的下一个可能的解决方案是写入该文件的专用进程。其余进程将通过命名管道和(可能)互斥体与日志进程通信。

      也许您甚至可以将其设置为没有显式进程,但其中一个正在运行的进程(首先启动的进程)承担此角色。当然,当这个过程结束并且需要将文件的所有权传递给另一个进程时,将会有进一步的复杂性。总而言之,这不是一个非常漂亮的解决方案,但如果一切都失败了,它应该可以工作。

      虽然我怀疑还有一些我们都没有想到的事情,因为有些程序可以成功地使用文件进行通信。

      嗯...再想一想-您已经有了可用的时间戳。为什么不制作一个按时间戳对记录进行排序的浏览工具呢?这样一来,缓存在哪里就无关紧要了。

      哦,还有第三个——你尝试过内存映射 I/O 吗?它的组织方式不同,它可能能够解决您的问题(更不用说更有效率了)。

      【讨论】:

      • 对于一个简单的跟踪解决方案,额外的过程是太多的开销。使用内存映射文件是一种选择,但它可能会很棘手,尤其是当您需要重新映射文​​件并创建滑动窗口时,否则当我保留整个文件时,我会占用整个地址空间(至少 32 位)映射。
      • @Alois Kraus - 这就是为什么建议将已经运行的进程之一指定为“主”的原因。但这是我刚刚想到的其他事情 - 文件中的“当前位置”指针是否在您的进程之间共享?也许这就是问题所在。也许它对于每个进程都是本地的,您需要在每次写入之前寻找到最后。
      • 输出没有被覆盖,但是另一个进程插入到另一个进程的行输出中间,这会破坏整个文件的格式。甚至看起来有可能得到一些东西 P11,P12,P13,P14,P21,P22,P1P23,P24,P15,5,P16 来自一个进程的输出被部分写入,然后在其余部分之前写入新的输出旧的输出被写入。很奇怪。
      • @Alois Kraus - 一位评论者似乎有一个想法 - 您是通过单个 WriteFile() 调用还是多个调用来编写消息?如果是多次调用,可能是 CPU 时间片干扰。
      • 每一行都会导致一个 WriteFile 调用。所以这不可能是问题。但我知道 Windows 内部会保留一个缓冲区并自行决定何时刷新缓冲区。似乎每个进程都有自己的缓冲区,这会导致在一行中间出现这个奇怪的换行符。
      【解决方案4】:

      作为一个起点,我建议放弃共享写入模式并使用您命名的互斥锁来管理对文件的独占访问。如果一切正常,这应该会给你正确的输出。然后做一些性能测试,看看你是否能负担得起这样的操作——你可能会发现这已经足够了。

      【讨论】:

        【解决方案5】:

        您必须保护或同步对使用命名互斥锁写入文件的函数的并发调用。见CreateMutex

        【讨论】:

        • 我已经这样做了。我想知道如何使它工作。全局互斥锁是不够的,因为似乎每个进程都有自己的缓冲区,当 Windows 认为它​​应该刷新到磁盘时。
        猜你喜欢
        • 2023-03-21
        • 1970-01-01
        • 1970-01-01
        • 2019-06-02
        • 1970-01-01
        • 2013-03-06
        • 2011-12-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多