我试图编写一个运行5个线程并相应打印其索引的程序。
下面是代码:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int nthreads=5;
void *busy(void* c) {
int my_busy = *(int *) c;
printf("Hello World with thread index %d\n", my_busy);
return NULL;
}
int main()
{
pthread_t t1[nthreads];
void* result;
for(int i=0; i<nthreads; i++)
{
pthread_create(&t1[i], NULL, busy, &i);
}
for(int i=0; i<nthreads; i++)
{
pthread_join(t1[i], &result);
}
return 0;
}
获得的输出:
Hello World with thread index 1
Hello World with thread index 4
Hello World with thread index 2
Hello World with thread index 0
Hello World with thread index 0
尽管所有5个线程都运行,为什么相应的索引未正确输出?为什么我倾向于放宽某些索引而使其他索引两次呢?例如,在这种情况下,我输了3,两次输出了0。
尽管在一个循环中将pthread_join
与pthread_create
一起使用可以解决此问题,但它不会安排所有线程并行运行。在这种情况下应该怎么做才能打印所有索引?
答案 0 :(得分:1)
虽然所有5个线程都运行,但为什么相应的索引未正确输出?
您向每个线程传递一个指向变量的指针,并在线程函数访问它的同时修改该变量。您为什么期望线程函数看到任何特定值? 它们同时运行。在某些体系结构上,如果一个线程正在读取值而另一个线程正在修改它,则线程可能会看到一个完全不可能的乱码。
例如,在这种情况下,我输了3,两次输出了0。
尽管例如由在创建每个线程函数之后,GCC会增加线程函数访问的变量,在某些体系结构上,由于未使用任何屏障或同步,因此线程函数观察到的值可能会“过时”。
这是否发生在您的特定计算机上(没有显式的障碍或同步),取决于您的计算机实施的memory ordering model。
例如,在x86-64(又名AMD64; 64位Intel / AMD架构)上,所有读取和写入均按顺序进行,除了可以在加载后对存储进行排序。这意味着,如果最初说i = 0;
,而线程A做了i = 1;
,那么即使线程A修改了变量,线程B仍然可以看到i == 0
。
请注意,添加障碍(例如,在使用大多数C编译器时使用_mm_fence()
提供的x86 / AMD64内在函数<immintrin.h>
)不足以确保每个线程看到唯一的值,因为每个线程的开头线程可能会相对于调用pthread_create()
时的现实时刻而延迟。他们所确保的只是至多一个线程看到零值。两个线程可能会看到值1,三个值会看到2,依此类推;甚至所有线程都可能看到值5。
在这种情况下应该如何打印所有索引?
最简单的选择是提供要打印的索引作为值,而不是作为变量的指针。在busy()中,使用
my_busy = (int)(intptr_t)c;
和main()中的
pthread_create(&t1[i], NULL, busy, (void *)(intptr_t)i);
intptr_t
类型是一种能够保存指针的有符号整数类型,并且在<stdint.h>
中定义(通常通过包含<inttypes.h>
来包含)。
(由于问题被标记为linux,所以我可能应该指出,在Linux上,在所有体系结构上,您都可以使用long
代替intptr_t
和unsigned long
uintptr_t
或long
中没有陷阱表示,并且每个可能的unsigned long
/ long
值都可以转换为唯一的{{ 1}},反之亦然;保证了往返是正确的。内核syscall接口要求这样做,因此将来也不太可能更改。)
如果需要将指针传递到unsigned long
,但希望每个线程看到唯一的值,则需要使用某种同步。
最简单的同步方法是使用信号量。您可以将其设置为全局,但是使用一种结构来描述工作参数,并传递该结构的指针(即使所有工作线程都使用相同的指针)也更可靠:
void *
由于主线程(修改每个工作线程看到的值的线程)参与了同步,因此每个工作函数在创建下一个线程之前先读取该值,输出将始终按{ {1}}。
一种更好的方法是创建一个工作池,其中主线程定义了要由线程共同完成的工作,而线程函数仅获取下一个要完成的工作块,以任何顺序:
i
如果编译并运行最后一个示例,您会注意到输出可能是奇数顺序的,但是每个#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
#define NTHREADS 5
struct work {
int i;
sem_t s;
};
void *worker(void *data)
{
struct work *const w = data;
int i;
/* Obtain a copy of the value. */
i = w->i;
/* Let others know we have copied the value. */
sem_post(&w->s);
/* Do the work. */
printf("i == %d\n", i);
fflush(stdout);
return NULL;
}
int main()
{
pthread_t thread[NTHREADS];
struct work w;
int rc, i;
/* Initialize the semaphore. */
sem_init(&w.s, 0, 0);
/* Create the threads. */
for (i = 0; i < NTHREADS; i++) {
/* Create the thread. */
w.i = i;
rc = pthread_create(&thread[i], NULL, worker, &w);
if (rc) {
fprintf(stderr, "Failed to create thread %d: %s.\n", i, strerror(rc));
exit(EXIT_FAILURE);
}
/* Wait for the thread function to grab its copy. */
sem_wait(&w.s);
}
/* Reap the threads. */
for (i = 0; i < NTHREADS; i++) {
pthread_join(thread[i], NULL);
}
/* Done. */
return EXIT_SUCCESS;
}
是唯一的,并且每个i
到#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#define NTHREADS 5
#define LOOPS 3
struct work {
pthread_mutex_t lock;
int i;
};
void *worker(void *data)
{
struct work *const w = data;
int n, i;
for (n = 0; n < LOOPS; n++) {
/* Grab next piece of work. */
pthread_mutex_lock(&w->lock);
i = w->i;
w->i++;
pthread_mutex_unlock(&w->lock);
/* Display the work */
printf("i == %d, n == %d\n", i, n);
fflush(stdout);
}
return NULL;
}
int main(void)
{
pthread_attr_t attrs;
pthread_t thread[NTHREADS];
struct work w;
int i, rc;
/* Create the work set. */
pthread_mutex_init(&w.lock, NULL);
w.i = 0;
/* Thread workers don't need a lot of stack. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);
/* Create the threads. */
for (i = 0; i < NTHREADS; i++) {
rc = pthread_create(thread + i, &attrs, worker, &w);
if (rc != 0) {
fprintf(stderr, "Error creating thread %d of %d: %s.\n", i + 1, NTHREADS, strerror(rc));
exit(EXIT_FAILURE);
}
}
/* The thread attribute set is no longer needed. */
pthread_attr_destroy(&attrs);
/* Reap the threads. */
for (i = 0; i < NTHREADS; i++) {
pthread_join(thread[i], NULL);
}
/* All done. */
return EXIT_SUCCESS;
}
都发生i
次。
答案 1 :(得分:0)
因为启动“忙”功能时索引值已经更改。最好将要传递给线程proc的参数分开保存。