pwrite写操作——原子操作,不改变当前文件操作符位置,写入位置offset是一个相对于fd起始地址的偏移量

本文深入探讨pwrite函数的特性,包括其原子操作属性、不影响文件指针位置的特点及相对于文件起始位置的偏移写入机制。同时,还讨论了在不同场景下pwrite与其他文件操作函数的区别和联系。

意外发现,pwrite() 函数是一个原子操作,现测试验证如下:

 

  1 /*
  2 pwrite相当于先调用lseek接着调用write。但又不完全是这样:
  3 (1)pwrite是原子操作,定位和写操作在一个原子操作中完成,期间不可中断。但分开的lseek和write中间可能被其他程序中断。
  4 (2)pwrite不更改当前文件的指针,也就是说不改变当前文件偏移量。
  5 (3)pwrite中的offset是一个绝对量,相对于文件开始处的绝对量,与当前文件指针位置无关。
  6 
  7 如下实例:
  8 
  9 */
 10 #include <fcntl.h>
 11 #include <unistd.h>
 12 #include <stdio.h>
 13 #include <errno.h>
 14 
 15 int main(void)
 16 {
 17     char pathName[] = {"/home/zll/201907-12/test"};
 18     int fd = -1;
 19     unsigned int offsetLen = 0;
 20     unsigned int writeLen;
 21     char bufData[] = {"0123456789asdfgh"};
 22     int ret;
 23 
 24     fd = open(pathName, O_RDWR | O_CREAT);
 25     if (fd < 0) {
 26         printf("open error: %s.\n", strerror(errno));
 27         return -1;
 28     }
 29 
 30     offsetLen = lseek(fd, 0, SEEK_SET);
 31     if (offsetLen < 0) {
 32         //printf("lseek error: %d.\n", perror(errno));
 33         perror("lseek error.\n");
 34         return -1;
 35     }
 36 
 37     writeLen = 10;
 38     ret = write(fd, bufData, writeLen);
 39     if (ret != writeLen) {
 40         printf("write error: %s.\n", strerror(errno));
 41         return -1;
 42     }
 43 // pwrite() 操作写文件时,只会按照偏移来写,不会影响当前的文件描述符位置;
 44 // (从文件描述符开头偏移两个字节处,写入从(&bufData[10])开始的4个字符)
 45 // 两个write() 之间增加pwrite(),丝毫未影响write操作的文件描述符位置
 46     writeLen = 4;
 47     ret = pwrite(fd, bufData + 10, writeLen, 2);
 48     if (ret != writeLen) {
 49         printf("pwrite error: %s.\n", strerror(errno));
 50         return -1;
 51     }
 52 
 53     writeLen = 6;
 54     ret = write(fd, bufData + 10, writeLen);
 55     if (ret != writeLen) {
 56         printf("write error: %s.\n", strerror(errno));
 57         return -1;
 58     }
 59 
 60     close(fd);
 61     return 0;
 62 }
 63 

a. 第一次write() 写入10个字符:0——9

b. 第二次pwrite() 从文件头的第2个字符开始写入从&bufData[10] 开始的4个字母,即:asdf

c. 第三次write() 写入bufData的最后6个字符,即:asdfgh

如上代码执行结果:

        

从上结果可知:同一个进程中,write() 写操作时,其系统内部会自动统计文件描述符的当前位置,pwrite() 操作是不会影响write() 操作的文件描述符指针的;

pwrite 操作,其写入位置是一个相对于文件头的偏移量,是一个相对的偏移地址。

 

 

————————————————————————————————————————————————————————

如果带有O_APPEND标志的情况,在文件真正写入之前会调用generic_write_checks进行一些检查,在检查的时候如果发现带有O_APPEND标志就将offset设置为文件的大小。而这整个过程都是在加锁的情况下完成的,所以带有O_APPEND标志的情况下,文件的写入是原子的,多线程写文件是不会导致数据错乱的。

另外一种情况就是pwrite系统调用,pwrite系统调用通过让用户指定写入的offset,值得整个写入的过程天然的变成原子的了,在上文说到,整个写入的过程是因为获取offset和文件写入是两个独立的步骤,并没有加锁,通过pwrite省去了获取offset这一步,最终整个文件写入只有一步加锁的文件写入过程了。

 

最后一个问题是多个进程写同一个文件是否会造成文件写错乱,直观来说是多进程写文件不是原子的,这是很显而易见的,因为每个进程都拥有一个struct file对象,是独立的,并且都拥有独立的文件offset,所以很显然这会导致上文中说到的数据覆盖的情况,但是否会导致数据错乱呢?,答案是不会,虽然struct file对象是独立的,但是struct inode是共享的(相同的文件无论打开多少次都只有一个struct inode对象),文件的最后写入其实是先要写入到页缓存中,而页缓存和struct inode是一一对应的关系,在实际文件写入之前会加锁,而这个锁就是属于struct inode对象(见上文中的mutex_lock(&inode->i_mutex))的,所有无论有多少个进程或者线程,只要是对同一个文件写数据,拿到的都是同一把锁,是线程安全的,所以也不会出现数据写错乱的情况。
--------------------- 
作者:zhangyifei216 
来源:优快云 
原文:https://blog.youkuaiyun.com/zhangyifei216/article/details/76653746 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

 

 

 

<think>首先,用户的问题是关于多线程环境下对同一个文件描述符进行读操作的可行性及注意事项。具体问题是:“多线程环境下一个线程一个线程读同一个文件描述符是否可行?” 根据系统级指令: - 所有行内数学表达式使用 `$...$` 格式。 - 独立公式使用 `$$...$$` 格式并单独成段。 - LaTeX 语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考提供的引用: - [^1]: 多线程共享进程的“文件描述符表”,多个线程的FD可能指向同一个文件表项”。文件表项中的“文件偏移量”是共享的——线程A修改了偏移量,线程B的下一次操作会从新位置开始。例子:两个线程同一文件做同步会怎样? - [^2]: 在多线程环境下访问共享资源时,必须使用锁保护所有访问,否则会有竞争条件风险。建议在文件描述符上使用读锁。 - [^3]: 多线程调用同一个对象的方法时,需要同步技术如互斥锁来保证线程安全。 这些引用强调了在多线程中共享文件描述符时,文件偏移量是共享的,因此读操作需要同步以避免竞争条件。 关键点: - 文件描述符在进程中是共享的,因为所有线程共享同一个进程地址空间。 - 文件表项中的文件偏移量是共享的:如果一个线程写入改变偏移量,另一个线程的读操作会从新的位置开始。 - 这可能导致数据一致或错误,例如,一个线程在数据时,另一个线程可能读取到错误的位置。 - 因此,需要同步机制,如互斥锁或读锁,来协调对文件描述符的访问。 回答结构: 1. **直接回答问题**:可行,但需要注意事项。 2. **解释原因**:基于文件描述符共享的原理。 3. **注意事项**:列出关键点,如同步机制。 4. **示例或建议**:给出代码示例或最佳实践。 5. **生成相关问题**:在回答结束时添加相关问题。 生成相关问题: - 基于主题,生成2-3个相关问题,如: 1. 如何实现多线程读文件描述符的同步? 2. 在多线程环境中,文件偏移量共享会带来哪些问题? 3. 除了锁,还有其他同步机制吗? 使用中文回答。 LaTeX:如果有数学表达式,但这个问题可能需要复杂的数学,所以可能需要。如果有,确保正确格式。 最终回答框架: - 开头:是的,可行,但需要同步。 - 解释:文件描述符共享和文件偏移量问题。 - 注意事项:使用锁、读锁等。 - 引用:自然地引用提供的引用。 - 相关问题:在结尾添加。</think>### 多线程环境下对同一个文件描述符进行读操作的可行性及注意事项 是的,在多线程环境下,一个线程一个线程读同一个文件描述符是可行的,但需要严格的同步机制来避免竞争条件和数据一致问题。以下我将逐步解释原因、潜在风险及注意事项,确保回答真实可靠。 #### 1. **可行性分析** - 在多线程环境中,所有线程共享同一个进程的“文件描述符表”(File Descriptor Table),因此多个线程可以访问同一个文件描述符(FD)。例如,线程A使用 `write()` 函数写入数据,线程B使用 `read()` 函数读取数据,这在技术上是允许的。 - 文件描述符指向的“文件表项”(File Table Entry)是共享的,其中包含关键信息如文件偏移量(File Offset)。线程A写入数据后,文件偏移量会被更新;线程B的读取操作会从新偏移量开始,这可能导致数据错位或丢失[^1]。 - 数学表达:文件偏移量的变化可以用公式描述。设初始偏移量为 $O_0$,线程A写入 $n$ 字节后,偏移量变为 $O_1 = O_0 + n$。线程B读取时,会从 $O_1$ 开始,而非 $O_0$。 #### 2. **主要风险(无同步时)** 如果进行同步,多线程读一个文件描述符会导致严重问题: - **竞争条件(Race Condition)**:线程A和线程B可能同时访问文件偏移量,导致读取位置错误或写入覆盖。例如: - 线程A写入数据后更新偏移量,线程B读取时可能跳过部分数据。 - 线程B读取时,线程A同时写入,导致读取到完整或损坏的数据。 - 引用[^1]中的例子:两个线程同一文件做同步时,数据可能乱序或丢失。 - **数据一致**:文件偏移量共享意味着线程操作相互影响。例如,线程A写入后,线程B的 `read()` 操作可能从错误位置开始,导致应用程序逻辑错误。 - **性能问题**:频繁的竞争会增加上下文切换开销,降低系统效率。 #### 3. **注意事项与解决方案** 为确保安全读,必须使用同步机制保护共享资源。以下是关键注意事项: - **使用锁机制**: - **读锁(Read-Write Lock)**:推荐使用读锁(如 `pthread_rwlock_t`),因为它允许多个线程并发读取,但操作独占访问。这能平衡性能与安全。 - 示例伪代码: ```c pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; // 线程A(操作) pthread_rwlock_wrlock(&lock); write(fd, buffer, size); // 写入文件 pthread_rwlock_unlock(&lock); // 线程B(读操作) pthread_rwlock_rdlock(&lock); read(fd, buffer, size); // 读取文件 pthread_rwlock_unlock(&lock); ``` - 引用[^2]强调:访问共享资源时,必须使用锁保护所有操作,否则会引入危险的竞争条件。 - **互斥锁(Mutex)**:如果读操作频繁,可以使用互斥锁(如 `pthread_mutex_t`),但可能降低并发性能。 - **避免共享文件偏移量**: - 使用 `pread()` 和 `pwrite()` 函数替代 `read()` 和 `write()`,这些函数允许指定偏移量,避免全局偏移量共享。例如: ```c // 线程B读取(指定偏移量) pread(fd, buffer, size, offset); // offset 是独立位置 ``` 这样,每个线程的读操作影响彼此的偏移量。 - **错误处理**: - 检查系统调用返回值(如 `read()` 或 `write()` 返回错误码),处理 EINTR(中断信号)或 EAGAIN(非阻塞模式)。 - 确保文件描述符在关闭时同步,避免“文件描述符泄漏”。 - **线程安全设计**: - 将文件描述符封装在对象中,并使用锁保护访问,如引用[^3]所述:多线程调用同一对象的方法时,需使用同步技术(如互斥锁)保证安全。 - 避免在多个线程中关闭文件描述符,除非使用锁协调。 #### 4. **最佳实践总结** - **可行但需同步**:读操作可行,但必须使用读锁或互斥锁同步。 - **性能考虑**:在高并发场景,优先使用 `pread()`/`pwrite()` 减少锁争用。 - **测试建议**:在开发中模拟多线程读,使用工具如 Valgrind 检测竞争条件。 - 引用[^1]和[^2]的核心点:多线程共享文件描述符表时,文件偏移量是共享的,必须用锁保护所有访问。 通过以上步骤,您可以安全地在多线程环境中实现读操作。如果文件操作频繁,建议评估使用内存映射文件(mmap)或其他IPC机制以减少锁开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值