

linux多线程编程中IO读写的安全函数,pread/pwrite和read/write有什么区别和联系,实...
source link: https://blog.popkx.com/linux-multithreaded-programming-in-io-read-write-security-functions-pread-pwrite-and-read-write-what-is-the-difference-and-relat/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

pread
和 pwrite
函数是 linux 下 C 语言编程中非常好用的 IO 操作函数。它们属于系统调用,在 2.1.60 之后版本的 linux 下都可以使用,尤其适合用于多线程的应用
中,它们允许多个线程操作同一个文件描述符,不会互相影响彼此的文件偏移(offset)。
pread 和 pwrite 函数
所需头文件和函数原型
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
pread
和pwrite
函数从文件描述符 fd
(可由open函数获得)描述的文件的 offset
偏移处,读写 count
字节数据。
如果是读,读出的内容通过
buf
传出。如果是写,写入的内容由buf
传入。
它俩的返回值与read
和write
函数类似,成功时,pread
和pwrite
函数分别返回成功读出或者写入的字节数。如果调用失败,返回 -1,失败码可从errno
查出。
pread/pwrite 和 read/write 函数的联系和区别
以写入函数为例,pwrite
函数在单线程
的情况下,相当于 lseek
和write
函数的组合:
pwrite(fd, buf, len, offset);
/** 相当于 */
lseek(fd, offset, SEEK_SET);
write(fd, buf, len);
只不过,在 fd
文件描述符没有O_APPEND
属性的情况下,write
函数会修改文件偏移指针,下次不seek
写入位置直接调用write
函数时,write
函数会接着上次的位置继续写入(offset+len)。而pwrite
函数则不会改变文件偏移指针。
另外,很重要的一点是,pwrite
函数的seek
和写入
是原子操作,不会因为进程调度或者其他因素中断。而对于lseek
和write
的组合,因为两个语句之间存在间隙,进程调度线程切换有可能发生在其间,导致写入的数据出错。
pread/pwrite 和 read/write 函数的实例demo对比
下面这段代码的功能很简单,就是创建了两个线程,一个线程负责顺序往 test.bin
文件顺序写入 0~254 的整数,另外一个线程则负责从中顺序读出。编译后,程序接收一个参数,1
表示只从test.bin
中读取,2
表示只往test.bin
中写入,3
表示同时读写test.bin
。
lseek
和read/write
的组合在多线程同时读写中的表现
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int fd;
#define MAX 255
#define filename "test.bin"
void* r_thread(void* p)
{
int i = 0, j = 0;
for(i=0; i<MAX; i++){
usleep(100);
lseek(fd, i, SEEK_SET);
read(fd, &j, 1);
// pread(fd, &j, 1, i);
printf("%s: %d\n", __FUNCTION__, j);
}
return NULL;
}
void* w_thread(void* p)
{
int i = 0, j = 0;
for(i=0; i<MAX; i++){
usleep(100);
lseek(fd, i, SEEK_SET);
write(fd, &i, 1);
// pwrite(fd, &j, 1, i);
printf("%s: %d\n", __FUNCTION__, j);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t ppid;
fd = open(filename, O_RDWR|O_CREAT, 0777);
if(argc<2){
printf("\n usage: %s [1(read),2(write),3(both read and write)]\n\n", argv[0]);
exit(1);
}
switch(atoi(argv[1])){
case 1:
pthread_create(&ppid, NULL, r_thread, NULL);
break;
case 2:
pthread_create(&ppid, NULL, w_thread, NULL);
break;
case 3:
pthread_create(&ppid, NULL, r_thread, NULL);
pthread_create(&ppid, NULL, w_thread, NULL);
break;
default:
printf("\n usage: %s [1(read),2(write),3(both read and write)]\n\n", argv[0]);
break;
}
getchar();
return 0;
}
我们编译,执行,发现有些数字并没有被顺序写入。
$ gcc pread_vs_read.c -lpthread
$ ./a.out 3
w_thread: 0
r_thread: 0
r_thread: 254
...
$ ./a.out 1
...
r_thread: 43
r_thread: 211
r_thread: 210
r_thread: 46
r_thread: 208
r_thread: 48
r_thread: 52
r_thread: 202
r_thread: 54
...
原因上面已经分析,就是因为lseek
和read/write
的组合并不是原子操作,而且read/write
函数也会改变文件偏移指针,这都会导致多线程同时读写时的数据紊乱。
pread/pwrite
在多线程同时读写中的表现
现在来试试pread/pwrite
函数的表现如何,我们将代码中的lseek
和read/write
注释,取消pread/pwrite
的注释:
...
// lseek(fd, i, SEEK_SET);
// read(fd, &j, 1);
pread(fd, &j, 1, i);
...
// lseek(fd, i, SEEK_SET);
// write(fd, &i, 1);
pwrite(fd, &i, 1, i);
...
再编译,做相同的测试:
$ gcc pread_vs_read.c -lpthread
lcc@lcc-vm:/lccRoot/xx_test/tmp/pread_vs_read$ ./a.out 3
w_thread: 0
r_thread: 0
w_thread: 1
r_thread: 1
...
$ ./a.out 1
r_thread: 0
r_thread: 1
r_thread: 2
r_thread: 3
r_thread: 4
r_thread: 5
...
发现数据被正确写入了。
可以看出,pread/pwrite
更适合在多线程编程中使用。当然,非要用read/write
也可以,只是需要自行做好线程同步,这就比pread/pwrite
麻烦了许多。
Recommend
-
42
最近在做 3d engine 时发现,我们使用的渲染 api 库 bgfx 提供的 log 回调函数是需要自己保证线程安全的。也就是说 bgfx 有可能在不同线程(采用多线程渲染时)调用这个 log 回调函数。 如果回调函数仅仅只是把 log 串写入...
-
44
本文是 搞点事情!死磕Java并发编程。 中的一篇试读文章,更多文章,请参见:深入理解Java并发编程 什么是线程安全 线程安全,维基百科中的解释是:...
-
27
Java多线程并发读写锁ReadWriteLock实现原理剖析 ...
-
10
通过前面几节的介绍,相信朋友们都能搭建自己的 linux 开发环境了。接下来,小编决定介绍一下 linux 中 C语言程序开发中常用的一些函数,并在这一实践过程中进一步了解 linux 内核。这样不至于使学习过程太过枯燥,也能顺便积累一些开发经验。
-
14
您现在的位置:首页 --> 算法 --> 实现多线程对队列的读写操作(封装类) 实现多线程对队列的读写操...
-
6
一、常见的锁策略 1. 悲观锁 VS 乐观锁 悲观锁 : 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别 人想拿这个数...
-
7
V2EX › Linux Linux c 编程问题请教,使用 readv/writev 读写 iovec 数组,如何循环读取或写入直到全部字节传输完毕?
-
8
您可能在不知不觉中以不安全的方式编写 URL 你能发现这段代码中的错误吗? const url = `https://builder.io/api/v2/content ?model=${model}&locale=${locale}?query.text=${text}` const...
-
5
Java并发编程模式:探索不同的线程安全实现方式 作者:编程技术汇 2023-10-18 09:27:58 开发 选择适合自己业务场景的线程安全手段,是...
-
3
Java中多线程异步读写文件 当涉及到
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK