对文本

时间:2015-12-28 21:35:50

标签: c++ multithreading mmap memory-mapped-files

我问this有关如何阅读从偏移pos到偏移endmmap()的文本文件的问题。特别是文本文件由多个线程读取,代码如下:

void getNextKeyValue() {
    key = pos;//value is the actual file offset
    char *mmappedData = (char*) mmap(NULL, end-pos+1, PROT_READ, MAP_PRIVATE , fd, pos);
    assert(mmappedData != NULL);
    value.assign(mmappedData);
    assert(munmap(mmappedData, end-pos+1)==0);
    morePairs = false;
}

未报告的变量在其他地方声明并初始化。顺便提一下,以下代码读取 整个 文本文件,而不是偏移posend

另外,程序突然终止(没有错误输出)多个线程,而只有一个线程读取整个文件才能正确终止。

更新

关注this示例(您可以尝试我的版本,使用cout write HERE ./main main.cpp 10 20 {}},我发现我是做错了是我通过cout<<mmappedData<<endl打印了读取数据。如果我使用write(STDOUT_FILENO, mmappedData+pos-pa_offset, end-pos);,则会打印出正确的文本部分。

我仍然不理解的是为什么整个文本存储在mmappedData(或addr后面的链接示例中):mmap使用清楚说明读取的字节数是从第4个arg开始的第2个arg。

3 个答案:

答案 0 :(得分:1)

更新新信息:

您的问题是您误解了mmap的工作原理。 mmap映射内存页面,而不是字节;即使你只要求映射24个字节,它实际上会映射几千字节(在大多数系统上4KB),不能保证数据被NUL终止(实际上,除非映射到达文件的末尾) ,对于文本输入文件,它可能不会NUL终止)。仅使用std::string::assign作为参数的char *方法使用C风格的字符串逻辑;它只是一直在阅读字符,直到它到达NUL。如果你很幸运,那么mmap之后的页面是可读的并且包含NUL,它只是将NUL之前的所有内容复制到字符串中(或等效地执行cout << mmappedData时,将其写出来),如果没有,则在映射后尝试访问未映射的内存地址时会出现段错误。

如果目标是从std::stringchar*分配特定数量的字节,则需要使用assign的两个arg形式来创建开始和长度与您对write一样明确,然后它只会使用您需要的数据:

value.assign(mmappedData+pos-pa_offset, end-pos);

原始投机答案:

所提供的代码不够清晰,无法排除其他问题,但如果您不断重新读取值,并设置了NDEBUG(禁用assert s),那么您就可以了解问题。有两个重叠的问题:

  1. 您未检查mmap的返回值(即使启用了assert,也未正确检查; mmap的错误返回是MAP_FAILED,而不是NULL
  2. 你永远不会munmap - ing; munmap调用位于assert内,并且assert已禁用,预处理器会删除您声明的代码。
  3. 因此,如果您有大量线程一遍又一遍地调用此函数(特别是如果映射的区域很大),那么您最终会耗尽虚拟内存空间。如果每次调用映射1 MB,即使在64位系统(实际上只有47位用户模式虚拟地址空间)上,在~134M调用之后,您将耗尽虚拟地址空间。如果每次呼叫都映射1 GB,那么在~134K呼叫后你就会用完。当然,在32位系统上,您可以更快地达到更多的限制。

    除此之外,我还无法知道是否还有其他问题;如果它是共享数据,value.assign也很容易成为问题。

答案 1 :(得分:1)

以这种方式从文件中读取数据将是有效的单线程。每个 How should I do this? var InputValues=React.createClass({ handleClick:function(){ this.props.onUserInput(this.refs.idInputText.value,this.refs.nameInputText.value); }, render:function(){ return( <div> <form> <label id="id">Id: </label><br/> <input type="text" placeholder="Enter id" ref="idInputText" /> <br/><br/><label id="name">Name: </label><br/> <input type="text" placeholder="Enter Name" ref="nameInputText" /> <p> <button type="button" onClick={this.handleClick}>Submit</button> </p> </form> </div> ); } }); var DisplayEmployeeTable=React.createClass({ getInitialState:function(){ return{ id:'', nameText:'' } }, handleUserInput:function(idText,nameText) { this.setState({id:idText, nameText:nameText}); }, render:function(){ return( <div> <InputValues onUserInput = {this.handleUserInput}/> <EmployeeTable idText={this.state.id} nameText={this.state.nameText} /> </div> ); } }); var EmployeeTable = React.createClass({ render: function() { return ( <table> <thead> <tr> <th>Id</th> <th>Name</th> </tr> <tr> <td>{this.props.idText}</td> //pass array values <td>{this.props.nameText}</td> </tr> </thead> </table> ); } }); ReactDOM.render( <DisplayEmployeeTable/>, document.getElementById('container') ); / <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> </head> <body> <div id="container"></div> <script type="text/babel" src="src/employee.js"></script> </body> </html>对需要对进程的地址空间内存映射进行两次修改 - 并且只有一个线程可以在任何时候导致地址空间映射的更改。更糟糕的是,更改进程的地址空间映射本身就是一项昂贵的操作。

映射整个文件 - 并保留映射 - 并根据需要从映射中复制数据,或者使用不依赖于任何共享值或状态的低级IO调用,例如{ {3}}。由于您已经有一个文件的打开文件描述符mmap,如下所示:

munmap

请注意,您可以使用fd然后void getNextKeyValue() { char buffer[end-pos+1]; ssize_t result = pread(fd, buffer, end-pos+1L, pos); assert(result == ( end-pos+1L ) ); value.assign(buffer); morePairs = false; } - 这不是原子操作 - 另一个线程可以在线程调用后修改文件指针至lseek(),但在致电read()之前。

并且不要在每次调用lseek()时使用read() / newdelete / malloc()作为缓冲区 - 再次有效的单线程读取数据

答案 2 :(得分:0)

我的方法有一些与我有关的事情。首先,我无法想象有问题的文件实际上是一个文本文件,因为如果你只是用一个原始指针调用assign()并且似​​乎确定了大小,它似乎有效。这意味着您要么嵌入了NUL,要么总是将所有内容复制到下一个NUL(可能超出映射范围)。在任何情况下,代码都闻起来!这也是cout << ptr“失败”的原因,而write(stdout, ptr, size)有效,在第一种情况下strlen()调用中有缓冲区溢出。

其次,为什么要使用mmap()?我想您希望获得有关所需尺寸或时间的一些好处。但是,普通的旧C ++ IOStream可能会mmap()一个滑动窗口进入文件本身,这非常有效。但是,您必须让IOStreams轻松完成此操作:

// turn off some syncing that you shouldn't need
std::ios_base::sync_with_stdio(false);
// disable any character conversions
std::ifstream in;
in.imbue(std::locale::classic());
in.open(filename, std::ios_base::binary);

经典语言环境不包含任何非平凡的字符转换方面。这不应该是必要的,但它也没有明显的伤害。二进制标志用于告诉streambuffer它不应该执行任何CR / LF转换,这尤其会对MS Windows产生影响。

最后一点对C ++ IOStreams来说有点棘手的是定位。除了起始位置(默认构造的streampos)之外,您只应为从seekp()或{{1}收到的seekg()tellp()提供值}。但是,有希望:没有任何字符转换,字节偏移可能对您有用。如果没有,你仍然可以构建跟踪周围位置的代码,尽管它可能有点棘手。我可以想象你甚至不得不依赖于实现定义的行为。