LINUX.ORG.RU

Пропадают строки при многопоточной записи в stdout и перенаправлении вывода


0

2

Столкнулся с проблемой пропадания строк при многопоточной записи в STDOUT_FILENO (т.е. write(), не fwrite()).

Нашёл в интернете одну прошлогоднюю программу с описанием подобной проблемы и слегка её подпилил:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>

#define NTHREADS 10

volatile int flag = 0;
void *start(void *arg) {
    char msg[255];
    int res;
    snprintf(msg, sizeof(msg), "hi from %"PRIxPTR"\n", (uintptr_t)arg);
    if ((uintptr_t)arg == NTHREADS - 1)
        flag = 1;
    while (!flag) ; /* thread barrier */
#if 1
    res = write(STDOUT_FILENO,msg,strlen(msg));
    if (res != strlen(msg))
        fprintf(stderr,"Failure: %i %s\n",res,strerror(res));
#else
    fputs(msg,stdout);
#endif
#if 1 /* work extra hard to flush output (makes no difference) */
    fflush(NULL);
    fsync(STDOUT_FILENO);
    sync();
#endif
    return NULL;
}

int main() {
    uintptr_t i;
    pthread_t id[NTHREADS];

    for (i =0 ; i < sizeof(id) / sizeof(id[0]); i++) {
        int ret = pthread_create(&(id[i]),NULL,&start,(void *)i);
        if (ret)
            fprintf(stderr, "pthread_create: %s\n", strerror(ret));
    }
    for (i =0 ; i < sizeof(id) / sizeof(id[0]); i++) {
        pthread_join(id[i], NULL);
    }
    return 0;
}

Компилируем:

$ gcc -Wall tmp.c -lpthread

Запускаем:

$ ./a.out | wc -l
10

Кажется, что всё нормально, но перенаправим вывод в файл, и начнутся чудеса:

$ ./a.out >o && wc -l o
2 o
$ ./a.out >o && wc -l o
2 o
$ ./a.out >o && wc -l o
4 o
$ ./a.out >o && wc -l o
3 o
$ ./a.out >o && wc -l o
3 o
$ ./a.out >o && wc -l o
2 o
$ ./a.out >o && wc -l o
3 o

Но если перенаправлять в существующий файл (дописыванием в конец):

$ rm o && touch o && ./a.out >> o && wc -l o
10 o
$ rm o && touch o && ./a.out >> o && wc -l o
10 o
$ rm o && touch o && ./a.out >> o && wc -l o
10 o

ОС - убунта 11.4. Под рукой есть солярка, на ней проблема не наблюдается. Объясните, пожалуйста, в чём может быть проблема?

★★★★★

Последнее исправление: Sorcerer (всего исправлений: 1)

ну дак понятно же

попробуй писать в пайп. т.е. запускать программу как

./a.out | wc -l

если пропаданий не будет - значит это тупо рэйс кондишен в сдвиге текущей позиции в файле и записи в файл. Открытие на режим O_APPEND доказывает это так как там не имеет смсл операция SEEK и последующая запись.

А ещё. одновременная запись в один и тот же файл никогда не гарантировала верный результат. Например, данные двух строк могут перемешаться (если строки достаточно длинные). Поэтому, модифицируя общий ресурс в тхреадах не забываем про блокировочку. Вот stdio не забывает (!).

mmarkk
()
Ответ на: ну дак понятно же от mmarkk

> значит это тупо рэйс кондишен в сдвиге текущей позиции в файле и записи в файл

Так и я о том: хотелось бы, чтобы этого рэйс кондишена в линуксе не было.

одновременная запись в один и тот же файл никогда не гарантировала верный результат. Например, данные двух строк могут перемешаться (если строки достаточно длинные)

Тут Вы сами себе противоречите. :) Длинные строки и не пишу. А одновременная запись коротких строк без перемешивания гарантируется POSIX.

Поэтому, модифицируя общий ресурс в тхреадах не забываем про блокировочку

В данном случае блокировка не нужна, т.к. этот общий ресурс имеет встроенные средства синхронизации на уровне ядра.

Вот stdio не забывает

Это же буферизованный ввод-вывод, без блокировки там сложно обойтись.

Sorcerer ★★★★★
() автор топика
Ответ на: комментарий от Sorcerer

1. Это где это POSIX такое говорит? может я не знаю, хотелось бы прочитать. особенно про длину начиная до которой он гарантирует.

2. по ходу, в STDIO работает без глюка потому что, по видимому, он блокирует доступ к буферу, делает write(), разблокировывает буфер. поэтому по сути, получается, что доступ к файловому дескриптору получается через блокировку. надо проверять - код не смотрел.

mmarkk
()
Ответ на: комментарий от mmarkk

В секции RATIONALE для read(), на который есть ссылка из RATIONALE для write().

I/O is intended to be atomic to ordinary files and pipes and FIFOs. Atomic means that all the bytes from a single operation that started out together end up together, without interleaving from other I/O operations.

Тут не сказано про максимальную гарантированную длину атомарной записи в обычный файл (хотя про пайпы сказано - это PIBE_BUF).

Кроме того, для файлов, открытых с O_APPEND (а мы тут только про таким образом открытые файлы и говорим) сказано:

If the O_APPEND flag of the file status flags is set, the file offset shall be set to the end of the file prior to each write and no intervening file modification operation shall occur between changing the file offset and the write operation.

И на практике действительно никакого перекрытия не происходит, если записывать в один файл, открытый с O_APPEND, из разных процессов.

Sorcerer ★★★★★
() автор топика
Ответ на: комментарий от Sorcerer

только вот они не сказали насчёт одновременной записи из тхреадов, из DUP дескрипторов (как например, оговорваривается в epoll_wait). Возможно, атомарность соблюдается только если открыть два раза с помощью open(). Кстати да, попробуй — интересно.

mmarkk
()
Ответ на: комментарий от mmarkk
2.9.1 Thread-Safety

All functions defined by this volume of POSIX.1-2008 shall be thread-safe, except that the following functions1 need not be thread-safe.

write() нет среди исключений.

Sorcerer ★★★★★
() автор топика
Ответ на: комментарий от Sorcerer

И кроме того:

2.9.7 Thread Interactions with Regular File Operations

All of the following functions shall be atomic with respect to each other in the effects specified in POSIX.1-2008 when they operate on regular files or symbolic links:

[...список функций, включающих write()...]

If two threads each call one of these functions, each call shall either see all of the specified effects of the other call, or none of them.

Sorcerer ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.