我需要小程序
第一
// 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并看到函数中的静态var。
但正因为如此,用std :: string程序也一定不能正常工作
按照惯例我的意思是,它只输出数字和新行
对于我使用的测试:
./freebsd-threaded-basename | egrep -av '^[0-9\n\s]+$' | env LANG=c less
UPD 我尝试使用strdup()或strcpy()结果是一样的 - 不正常
UPD * 每个 *时间运行std :: string的版本按预期工作
答案 0 :(得分:2)
程序无法预测的原因是basename
,这不是线程安全的。 basename
有点过时了。现代C ++应用程序倾向于使用其他方法来解析文件路径。 Boost Filesystem Library很受欢迎,可能会用来做它。
如果您坚持使用basename
,请将其与一些代码一起放入关键部分,这些代码将获得basename
的结果(printf
或strcpy
,或其他一些)。这可以保证不会同时从多个线程访问basename
的结果。这意味着正确的行为。
现在有些猜测“为什么”。 (只是猜测,因为无法预测,非线程安全的多线程程序是如何工作的。)
程序的第一个版本执行basename
部分并行循环(basename
函数和循环本身),部分顺序执行(printf
和free
是线程安全函数,他们的实施受到关键部分的保护。)
第二个版本添加std::string
,这意味着更多的顺序代码。它为新字符串分配内存,释放旧内存(这些操作都是线程安全的,并受关键部分保护)。此外(在一些实现中)使用原子操作来更新共享计数器,这也降低了并行性。所有这些实际上将您的程序从并行转换为完全顺序。所有线程大多等待一些互斥锁。或者有时执行一些复杂的printf / memory / std :: string计算。并且很少有一个线程执行相对简单的basename
计算。几乎就像你在basename
附近添加了一个关键部分。
Linux测试的正确结果可能是因为printf
和free
足以使程序在这种情况下几乎是顺序的。 (因为在Linux中或者由于硬件不同,某些事情会有所不同)。
答案 1 :(得分:1)
来自Linux manual page on pthreads:
POSIX.1-2001和POSIX.1-2008要求标准中指定的所有函数都是线程安全的,但以下函数除外:
[功能列表]
basename()
因此basename
不保证是线程安全的(尽管某些实现可能会这样做)。如果您希望应用程序可移植,则必须使用互斥锁等方式保护呼叫。
另请参阅明确说明的POSIX参考:
basename()函数可以修改path指向的字符串,并且可以返回指向静态存储的指针,然后可以通过对basename()的后续调用来覆盖该指针。
basename()函数不必是线程安全的。
答案 2 :(得分:1)
FreeBSD上basename()的手册页对此进行了解释,您可以在此处找到:
http://www.freebsd.org/cgi/man.cgi?query=basename&sektion=3
特别是:
实施说明 basename()函数返回指向内部存储空间的指针 在第一次通话时输入,后续通话将覆盖该通话。 因此,basename_r()是线程应用程序的首选。
因此,从basename()返回的数据可能已被您正在使用的其他线程覆盖。使用basename_r可以防止这种情况。