多个线程循环运行时如何影响索引

时间:2018-09-23 02:08:50

标签: c linux pthreads

我试图编写一个运行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_joinpthread_create一起使用可以解决此问题,但它不会安排所有线程并行运行。在这种情况下应该怎么做才能打印所有索引?

2 个答案:

答案 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上,在所有体系结构上,您都可以使用long代替intptr_tunsigned long uintptr_tlong中没有陷阱表示,并且每个可能的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的参数分开保存。