虽然5个线程都运行了,为什么相应的索引没有正确输出?
您将指向变量的指针传递给每个线程,并在线程函数访问该变量的同时修改该变量。为什么您期望线程函数看到任何特定值?他们同时运行。在某些体系结构上,如果一个线程正在读取该值,而另一个线程正在修改该值,则线程可能会看到完全不可能的乱码值。
例如,在本例中,我丢失了 3,并且输出了 0 两次。
虽然由例如生成的机器代码GCC 在创建每个线程函数后递增线程函数访问的变量,在某些体系结构上线程函数观察到的值可能是“旧的”,因为没有使用屏障或同步。
这是否发生在您的特定机器上(没有明确的障碍或同步),取决于哪个内存排序模型 https://en.wikipedia.org/wiki/Memory_ordering你的机器实现了。
例如,在 x86-64(又名 AMD64;64 位 Intel/AMD 架构)上,所有读取和写入均按顺序观察,除了商店可以在装载后订购。这意味着如果最初说i = 0;
,线程 A 执行此操作i = 1;
,线程B仍然可以看到i == 0
即使在线程 A 修改了变量之后。
请注意,添加障碍(例如_mm_fence()
使用 x86/AMD64 提供的内在函数<immintrin.h>
当使用大多数 C 编译器时)不足以确保每个线程看到唯一的值,因为每个线程的启动可能会相对于现实世界的时刻有所延迟pthread_create()
被称为。他们所确保的是至多一个线程看到零值。两个线程可能看到值 1,三个线程可能看到值 2,依此类推;甚至所有线程都可能看到值 5。
在这种情况下应该怎么做才能打印所有索引?
最简单的选项是提供要打印的索引作为值,而不是作为指向变量的指针。在 busy() 中,使用
my_busy = (int)(intptr_t)c;
在 main() 中,
pthread_create(&t1[i], NULL, busy, (void *)(intptr_t)i);
The intptr_t
type 是能够保存指针的有符号整数类型,并在中定义<stdint.h>
(通常通过包含<inttypes.h>
反而)。
(由于问题被标记为linux /questions/tagged/linux,我可能应该指出,在 Linux 中,在所有架构上,您都可以使用long
代替intptr_t
, and unsigned long
代替uintptr_t
。两者都没有陷阱表示long
or unsigned long
,以及一切可能的long
/unsigned long
值可以转换为唯一的void *
,反之亦然;保证往返工作正常。内核系统调用接口要求这样做,因此将来也极不可能改变。)
如果您需要将指针传递给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
都是独一无二的,并且每一个n = 0
通过n = LOOPS-1
恰好发生NTHREADS
times.