【问题标题】:basename freebsd work correct with std::string, but without notbasename freebsd 与 std::string 一起工作正确,但没有
【发布时间】:2012-10-13 20:48:19
【问题描述】:

我要小程序

第一

// 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::string)不能工作
谁能解释一下原因?

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

我的意思是,它只输出数字和新行

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

UPD 我尝试使用 strdup() 或 strcpy() 结果是一样的 - 不正常
UPD *每个*运行带有 std::string 的版本时,它按预期工作

【问题讨论】:

  • 一个小点:为什么不在std::string的版本中直接赋值给tmpStr呢?喜欢tmpStr = basename(dataset[i]);
  • 因为我写这个小测试程序很快,我只是用你建议的那句话测试这段代码,结果是一样的

标签: c++ string freebsd


【解决方案1】:

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

如果您坚持使用basename,请将其与一些代码一起放置到临界区,这些代码将得到basename 的结果(可能是printf,或strcpy,或其他一些)。这保证了basename 的结果不会同时从多个线程访问。这意味着正确的行为。

现在对“为什么”进行一些猜测。 (只是猜测,因为无法预测非线程安全的多线程程序究竟是如何工作的)。

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

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

Linux 测试的正确结果可能是因为printffree 在这种情况下足以使程序几乎是连续的。 (因为在 Linux 中做的事情不同,或者因为不同的硬件)。

【讨论】:

  • 总结一下,因为std::string构造函数是线程安全的,并且等待一些互斥体,所有程序也等待它,并且因为这个数据可以正常读/写?但是当我将变量传递给std::string 构造函数时,这段代码可以是非线程安全的吗?
  • 不,这仍然不是线程安全的。一切看起来都不错,只是因为所有线程都忙于在互斥锁上同步。因此,即使使用std::string,总是有可能出现问题。此外,一个线程可能会打印另一个线程中生成的数字(这很难用随机数注意到)。
【解决方案2】:

来自Linux manual page on pthreads

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

[函数列表]

basename()

所以basename 不能保证是线程安全的(尽管某些实现可能会这样做)。如果您希望您的应用程序是可移植的,您必须使用互斥锁之类的东西来保护调用。

另请参阅POSIX 参考,其中明确指出:

basename() 函数可以修改 path 指向的字符串,并且可以返回一个指向静态存储的指针,然后可能会被后续调用 basename() 覆盖。

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

【讨论】:

  • @azat 每次运行带有std::string 的版本时,它都会按预期工作,但不会与其他版本一起运行?一直还是大部分时间?
【解决方案3】:

这在 FreeBSD 上的 basename() 手册页中有说明,您可以在此处找到:

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

特别是:

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

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

【讨论】:

  • 我应该补充一点,它在第二个程序中起作用的原因看起来纯粹是运气(和一些时间)。 std::string 将在其构造函数中复制字符串,这是线程在调用 basename() 后要做的第一件事,因此它将“立即”读取字符串。在没有 std::string 的实现中,我们在 basename() 调用之后进入 printf,它需要在实际对给定字符串执行任何操作之前处理变量参数并解析格式字符串。因此,到那时它被覆盖的可能性要高得多。
  • 不,这不是纯粹的运气或构造函数中的应对字符串。我尝试使用strdup()strcpy() 结果是一样的——不正常
猜你喜欢
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
  • 2018-08-30
  • 2013-05-28
  • 2021-10-09
  • 1970-01-01
  • 2015-09-15
  • 2010-09-22
相关资源
最近更新 更多