在纯c中更改线程中的数组/结构/ ..的部分而不阻塞整个事物

时间:2016-05-31 23:40:26

标签: c arrays multithreading thread-safety pthreads

我想在多个线程中修改数组(或结构)的一些(不是全部)字段,而不会阻塞数组的其余部分,因为其余部分正在其他线程中进行修改。这是如何实现的?我找到了一些答案,但它们适用于C ++,我想用C语言完成。 这是我到目前为止的代码:

#define _GNU_SOURCE

#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define ARRAYLENGTH 5
#define TARGET 10000

int target;

typedef struct zstr{
    int* array;
    int place;
    int run;
    pthread_mutex_t* locks;
}zstr;

void *countup(void *);

int main(int argc, char** args){
    int al;
    if(argc>2){
        al=atoi(args[1]);
        target=atoi(args[2]);
    }else{
        al=ARRAYLENGTH;
        target=TARGET;
    }
    printf("%d %d\n", al, target);
    zstr* t=malloc(sizeof(zstr));
    t->array=calloc(al, sizeof(int));
    t->locks=calloc(al, sizeof(pthread_mutex_t));
    int* rua=calloc(al, sizeof(int));
    pthread_t id[4*al];
    for(int i=0; i<al; i++)
            pthread_mutex_init(&(t->locks[i]), NULL);
    for(int j=0; j<4*al; j++){
        int st=j%al;
        t->run=rua[st]++;
        t->place=st;
        pthread_create(&id[j], NULL, &countup, t);
    }
    for(int k=0; k<4*al; k++){
        pthread_join(id[k], NULL);
    }
    for(int u=0; u<al; u++)
           printf("%d\n", t->array[u]);
    free(rua);
    free(t->locks);
    free(t->array);
    return 0;
}

void *countup(void* table){
    zstr* nu=table;
    if(!nu->run){
        pthread_mutex_lock(nu->locks + nu->place);
    }else{
        pthread_mutex_trylock(nu->locks + nu->place);
    }
    while(nu->array[nu->place]<target)
        nu->array[nu->place]++;
    pthread_mutex_unlock(nu->locks + nu->place); 
    return NULL;
}

有时候这很好用,但是然后计算错误的值和安静的排序问题(比如默认值),它需要超长(奇怪的是,当我把它们作为参数传递时它曾经工作过一次)。

2 个答案:

答案 0 :(得分:0)

关于数组或结构的一部分没有任何特殊之处。重要的是正确使用应用于给定值的互斥锁或其他同步。

在这种情况下,您似乎没有检查锁定功能结果。

countup函数的设计只允许单个线程访问对象,在释放锁之前将值一直运行到目标,但是不检查trylock结果。

所以可能发生的事情是第一个线程获取锁定,同一个互斥锁上的后续线程调用trylock并且无法获得锁定,但代码不检查结果。然后,您将获得多个线程,无需同步即可递增相同的值。给定所有指针解引用,索引和增量操作不能保证是原子的,导致值远远超出目标的问题。

故事的寓意是检查功能结果并处理错误。

答案 1 :(得分:0)

抱歉,还没有足够的声誉发表评论。

除了Brad没有检查pthread_mutex_trylock结果的评论之外,还有一个错误概念,它显示了Pthreads多次: 您假设pthread_create将立即启动,并接收传递的值(此处指针t到您的结构中)并且它的内容以原子方式读取。事实并非如此。该线程可能会在以后的任何时间启动,并且会发现t->runt->place之类的内容已经被main中j循环的下一次迭代所更改。

此外,您可能想阅读David Butenhof的书“用Posix线程编程”(旧的,但仍然是一个很好的参考)并检查同步和条件变量。

首先启动那么多线程并不是那么好的风格;)

由于这已经出现了几次并且可能再次出现,我已经重新构造了一些,以便向已启动的线程发出work_items。下面的代码可能会被一个函数修改,它将索引映射到array到始终是相同的area_lock,或者通过添加一个队列来为正在运行的线程提供更多的工作项......

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

/*
 * Macros for default values. To make it more interesting, set:
 *    ARRAYLENGTH != THREADS
 *    INCREMENTS != TARGET
 *    NUM_AREAS != THREADS
 * Please note, that NUM_AREAS must be <= ARRAY_LENGTH.
 */
#define ARRAYLENGTH 10
#define TARGET 100
#define INCREMENTS 10
#define NUM_AREAS 2
#define THREADS 5


/* These variables are initialized once in main, then only read... */
int array_len;
int target;
int num_areas;
int threads;
int increments;

/**
 * A long array that is going to be equally split into number of areas.
 * Each area is covered by a lock. The number of areas do not have to
 * equal the length of the array, but must be smaller...
 */
typedef struct shared_array {
    int * array;
    int num_areas;
    pthread_mutex_t * area_locks;
} shared_array;

/**
 * A work-item a thread is assigned to upon startup (or later on).
 * Then a value of { 0, any } might signal the ending of this thread.
 * The thread is working on index within zstr->array, counting up increments
 * (or up until the target is reached).
 */
typedef struct work_item {
    shared_array * zstr;
    int work_on_index;
    int increments;
} work_item;


/* Local function declarations */
void * countup(void *);

int main(int argc, char * argv[]) {
    int i;
    shared_array * zstr;

    if (argc == 1) {
        array_len = ARRAYLENGTH;
        target = TARGET;
        num_areas = NUM_AREAS;
        threads = THREADS;
        increments = INCREMENTS;
    } else if (argc == 6) {
        array_len = atoi(argv[1]);
        target = atoi(argv[2]);
        num_areas = atoi(argv[3]);
        threads = atoi(argv[4]);
        increments = atoi(argv[5]);
    } else {
        fprintf(stderr, "USAGE: %s len target areas threads increments", argv[0]);
        exit(-1);
    }
    assert(array_len >= num_areas);

    zstr = malloc(sizeof (shared_array));
    zstr->array = calloc(array_len, sizeof (int));
    zstr->num_areas = num_areas;
    zstr->area_locks = calloc(num_areas, sizeof (pthread_mutex_t));

    for (i = 0; i < num_areas; i++)
        pthread_mutex_init(&(zstr->area_locks[i]), NULL);

    pthread_t * id = calloc(threads, sizeof (pthread_t));
    work_item * work_items = calloc(threads, sizeof (work_item));

    for (i = 0; i < threads; i++) {
        work_items[i].zstr = zstr;
        work_items[i].work_on_index = i % array_len;
        work_items[i].increments = increments;
        pthread_create(&(id[i]), NULL, &countup, &(work_items[i]));
    }
    // Let's just do this one work-item.
    for (i = 0; i < threads; i++) {
        pthread_join(id[i], NULL);
    }

    printf("Array: ");
    for (i = 0; i < array_len; i++)
        printf("%d ", zstr->array[i]);
    printf("\n");

    free(id);
    free(work_items);
    free(zstr->area_locks);
    free(zstr->array);
    return 0;
}

void *countup(void* first_work_item) {
    work_item * wi = first_work_item;
    int inc;

    // Extract the information from this work-item.
    int idx = wi->work_on_index;
    int area = idx % wi->zstr->num_areas;
    pthread_mutex_t * lock = &(wi->zstr->area_locks[area]);

    pthread_mutex_lock(lock);
    for (inc = wi->increments; inc > 0 && wi->zstr->array[idx] < target; inc--)
        wi->zstr->array[idx]++;
    pthread_mutex_unlock(lock);

    return NULL;
}