freebsd basename корректно работает с std :: string, но без - PullRequest
2 голосов
/ 30 января 2012

У меня есть небольшие программы

Первая

// compile with -lpthread
// TEST:
// basename


#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
#include <limits.h>
#include <inttypes.h>


// DATASET_LEN
#ifndef DATASET_LEN
#define DATASET_LEN 10000
#endif
// THREADS_NUM
#ifndef THREADS_NUM
#define THREADS_NUM 16
#endif


// need to call free(3) after
char** generateArray() {
    char** dataset = (char**)malloc(sizeof(char*) * DATASET_LEN);
    // fill dataset
    for (size_t i = 0; i < DATASET_LEN; ++i) {
        dataset[i] = (char*)malloc(sizeof(char) * CHAR_MAX);
        sprintf(dataset[i], "%i/%i/", rand(), rand());
    }

    return dataset;
}

// pthread_create(3) callback
void* run(void* args) {
    char** dataset = generateArray();
    char* baseName;

    for (size_t i = 0; i < DATASET_LEN; ++i) {
        baseName = basename(dataset[i]);
        printf("%s\n", baseName);

        free(dataset[i]);
    }

    free(dataset);
}

// main
int main(int argc, char** argv) {
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * THREADS_NUM);
    // threads start
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_create(&threads[i-1], NULL, run, NULL);
        fprintf(stderr, "Thread %u started\n", i);
    }
    // threads join
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_join(threads[i-1], NULL);
        fprintf(stderr, "Thread %u finished\n", i);
    }
    free(threads);

    return EXIT_SUCCESS;
}

Вторая:

// compile with -lpthread
// TEST:
// basename


#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
#include <limits.h>
#include <inttypes.h>
#include <string>


// DATASET_LEN
#ifndef DATASET_LEN
#define DATASET_LEN 10000
#endif
// THREADS_NUM
#ifndef THREADS_NUM
#define THREADS_NUM 16
#endif


// need to call free(3) after
char** generateArray() {
    char** dataset = (char**)malloc(sizeof(char*) * DATASET_LEN);
    // fill dataset
    for (size_t i = 0; i < DATASET_LEN; ++i) {
        dataset[i] = (char*)malloc(sizeof(char) * CHAR_MAX);
        sprintf(dataset[i], "%i/%i/", rand(), rand());
    }

    return dataset;
}

// pthread_create(3) callback
void* run(void* args) {
    char** dataset = generateArray();
    char* baseName;
    std::string tmpStr;

    for (size_t i = 0; i < DATASET_LEN; ++i) {
        baseName = basename(dataset[i]);
        tmpStr = std::string(baseName);
        printf("%s\n", tmpStr.c_str());

        free(dataset[i]);
    }

    free(dataset);
}

// main
int main(int argc, char** argv) {
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * THREADS_NUM);
    // threads start
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_create(&threads[i-1], NULL, run, NULL);
        fprintf(stderr, "Thread %u started\n", i);
    }
    // threads join
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_join(threads[i-1], NULL);
        fprintf(stderr, "Thread %u finished\n", i);
    }
    free(threads);

    return EXIT_SUCCESS;
}

Обе программы работают нормально в linux, но сначала на freebsd (без std:: строка) не работаетКто-нибудь может объяснить почему?

Я вижу freebsd src в /usr/src/lib/libc/gen/basename.c и вижу статическую переменную в функции.Но из-за этого с std :: string программа также не должна работать нормально

Под нормальным я имею в виду, что она выводит только цифры, а новые строки

Для тестов я использую: ./freebsd-threaded-basename | egrep -av '^[0-9\n\s]+$' | env LANG=c less

UPD Я пытаюсь использовать strdup () или strcpy (), результат один и тот же - ненормально UPD * Каждый * каждый раз, когда запускается версия с std :: string, она работает как положено

Ответы [ 3 ]

2 голосов
/ 21 февраля 2012

Причиной непредсказуемого поведения ваших программ является basename, что не является поточно-ориентированным. basename немного устарел. Современные приложения на C ++ обычно используют другие средства для анализа пути к файлу. Библиотека Boost Filesystem популярна и может использоваться для этого.

Если вы настаиваете на использовании basename, поместите его в критическую секцию вместе с некоторым кодом, который получит результат basename (будь то printf, или strcpy, или какой-либо другой). Это гарантирует, что результат basename не будет доступен из нескольких потоков одновременно. Что означает правильное поведение.

Теперь немного догадаться о "почему". (Только догадки, потому что невозможно предсказать, как именно будет работать многопоточная многопоточная программа).

Первая версия вашей программы выполняет цикл basename частично параллельно (функция basename и сам цикл), частично последовательно (printf и free являются поточно-ориентированными функциями, их реализация защищена критическими секциями ).

Вторая версия добавляет std::string, что означает намного более последовательный код. Он выделяет память для новой строки, освобождает старую память (обе эти операции являются поточно-ориентированными и защищены критическими секциями). Также (в некоторых реализациях) используются атомарные операции для обновления общего счетчика, что также снижает параллелизм. Все это фактически превращает вашу программу из параллельной в полностью последовательную. Все потоки в основном ждут какой-нибудь мьютекс. Или иногда выполнять сложные вычисления printf / memory / std :: string. И очень редко один из потоков выполняет относительно простые вычисления basename. Как будто вы добавили критический раздел около basename.

Возможно, правильные результаты для тестов Linux объясняются тем, что printf и free достаточно, чтобы в этом случае программа была почти последовательной. (Потому что что-то делается по-другому в Linux или из-за другого оборудования).

1 голос
/ 30 января 2012

Это объясняется на странице руководства для basename () во FreeBSD, которую вы можете найти здесь:

http://www.freebsd.org/cgi/man.cgi?query=basename&sektion=3

В частности:

ЗАМЕЧАНИЯ ПО РЕАЛИЗАЦИИ Функция basename () возвращает указатель на внутреннюю память на первый вызов, который будет перезаписан последующими вызовами. Поэтому basename_r () предпочтительнее для многопоточных приложений.

Таким образом, данные, полученные вами из basename (), могут быть перезаписаны другими потоками, которые вы используете. Использование basename_r предотвращает это.

1 голос
/ 30 января 2012

На странице руководства Linux по pthreads :

POSIX.1-2001 и POSIX.1-2008 требуют, чтобы все функции, указанные в стандарте, были поточно-ориентированными, за исключением следующих функций:

[Список функций]

basename()

Таким образом, basename не гарантируется поточно-ориентированным (хотя некоторые реализации могут это делать). Если вы хотите, чтобы ваше приложение было переносимым, вы должны защитить вызов чем-то вроде мьютекса.

Также см. Ссылку POSIX , где прямо указано:

Функция basename () может изменять строку, указанную в path, и может возвращать указатель на статическое хранилище, которое затем может быть перезаписано последующим вызовом basename ().

Функция basename () не обязательно должна быть поточно-ориентированной.

...