basename-freebsd使用std::string可以正常工作,但不使用

basename freebsd work correct with std::string, but without not

本文关键字:工作 常工作 string std 使用 basename-freebsd      更新时间:2023-10-16

我必须对进行小程序

第一个

// 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("%sn", 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 startedn", i);
    }
    // threads join
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_join(threads[i-1], NULL);
        fprintf(stderr, "Thread %u finishedn", 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("%sn", 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 startedn", i);
    }
    // threads join
    for (int i = 1; i <= THREADS_NUM; ++i) {
        pthread_join(threads[i-1], NULL);
        fprintf(stderr, "Thread %u finishedn", i);
    }
    free(threads);
    return EXIT_SUCCESS;
}

这两个程序在linux下都能正常工作,但首先在freebsd上(没有std::string)不能工作
有人能解释为什么吗?

我在/usr/src/lib/libc/gen/basename.c中看到了freebsd-src,并在函数中看到了一个静态var
但正因为如此,用std::string程序也不能正常工作

正常情况下,我的意思是,它只输出数字和新的行

对于我使用的测试:./freebsd-threaded-basename | egrep -av '^[0-9ns]+$' | env LANG=c less

UPD我尝试使用strdup()或strcpy()结果相同-不正常
UPD*每次运行带有std::string的版本时,它都会按预期运行

程序行为不可预测的原因是basename,它不是线程安全的。basename有点过时了。现代C++应用程序倾向于使用其他方法来解析文件路径。Boost文件系统库很受欢迎,可以用来做这件事。

如果您坚持使用basename,请将其与一些代码一起放在关键部分,这些代码将获得basename的结果(无论是printfstrcpy还是其他代码)。这保证了basename的结果不会同时从多个线程访问。这意味着正确的行为。

现在有一些关于"为什么"的猜测。(由于无法预测非线程安全的多线程程序究竟是如何工作的,所以只能猜测)。

程序的第一个版本部分并行执行basename循环(basename函数和循环本身),部分按顺序执行(printffree是线程安全函数,它们的实现受到关键部分的保护)。

第二个版本添加了std::string,这意味着更多的顺序代码。它为一个新字符串分配内存,释放旧内存(这两种操作都是线程安全的,并受到关键部分的保护)。此外(在某些实现中)is使用原子操作来更新共享计数器,这也降低了并行性。所有这些实际上都将您的程序从并行转换为完全顺序。所有线程大多都在等待某个互斥对象。或者有时执行一些复杂的printf/memory/std::字符串计算。并且很少有一个线程进行相对简单的CCD_ 15计算。几乎就像你在basename周围添加了一个关键部分。

Linux测试的正确结果可能是因为printffree足以使程序在这种情况下几乎按顺序运行。(因为Linux中的某些操作方式不同,或者硬件不同)。

从pthreads:上的Linux手册页面

POSIX.1-2001和POSIX.1-2008要求标准中规定的所有功能都应是线程安全的,但以下功能除外:

【功能列表】

basename()

因此,不能保证basename是线程安全的(尽管有些实现可能会这样做)。如果你想让你的应用程序是可移植的,你必须用互斥锁之类的东西来保护调用。

另请参阅POSIX参考,其中明确表示:

basename()函数可以修改路径指向的字符串,并可以返回一个指向静态存储的指针,该指针随后可能会被对basename(()的调用覆盖。

basename()函数不需要是线程安全的。

这在FreeBSD上basename()的手册页中有解释,您可以在这里找到:

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

特别是:

实施说明basename()函数返回一个指向内部存储空间allo的指针-在将被后续调用覆盖的第一个调用上指定。因此,basename_r()是线程应用程序的首选项。

因此,从basename()返回的数据可能已被正在使用的其他线程覆盖。使用basename_r可以防止这种情况。