17

linux多线程编程中IO读写的安全函数,pread/pwrite和read/write有什么区别和联系,实...

 4 years ago
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.
neoserver,ios ssh client

preadpwrite 函数是 linux 下 C 语言编程中非常好用的 IO 操作函数。它们属于系统调用,在 2.1.60 之后版本的 linux 下都可以使用,尤其适合用于多线程的应用中,它们允许多个线程操作同一个文件描述符,不会互相影响彼此的文件偏移(offset)。

b57f34b3d954b991058d73b8957e29d0.png

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);

preadpwrite函数从文件描述符 fd(可由open函数获得)描述的文件的 offset 偏移处,读写 count 字节数据。

如果是读,读出的内容通过buf传出。如果是写,写入的内容由buf传入。

它俩的返回值与readwrite函数类似,成功时,preadpwrite函数分别返回成功读出或者写入的字节数。如果调用失败,返回 -1,失败码可从errno查出。

pread/pwrite 和 read/write 函数的联系和区别

以写入函数为例,pwrite函数在单线程的情况下,相当于 lseekwrite函数的组合:

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写入是原子操作,不会因为进程调度或者其他因素中断。而对于lseekwrite的组合,因为两个语句之间存在间隙,进程调度线程切换有可能发生在其间,导致写入的数据出错。

e6744b75c7ffffe49f905c09d0aba4b1.png

pread/pwrite 和 read/write 函数的实例demo对比

下面这段代码的功能很简单,就是创建了两个线程,一个线程负责顺序往 test.bin 文件顺序写入 0~254 的整数,另外一个线程则负责从中顺序读出。编译后,程序接收一个参数,1表示只从test.bin中读取,2表示只往test.bin中写入,3表示同时读写test.bin

lseekread/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
...

原因上面已经分析,就是因为lseekread/write的组合并不是原子操作,而且read/write函数也会改变文件偏移指针,这都会导致多线程同时读写时的数据紊乱。

eff43b0dc944442c82643bda4425560f.png

pread/pwrite在多线程同时读写中的表现

现在来试试pread/pwrite函数的表现如何,我们将代码中的lseekread/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
...

发现数据被正确写入了。

57de992957b750bd151b22e9617648e3.png

可以看出,pread/pwrite更适合在多线程编程中使用。当然,非要用read/write也可以,只是需要自行做好线程同步,这就比pread/pwrite麻烦了许多。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK