无法重新获取互斥锁并在线程之间正确传递值

时间:2016-02-07 01:19:05

标签: c multithreading synchronization pthreads

我正在尝试实现代码来练习同步,所以可能不是最好的设计或方法,但目标如下

  • 主线程

    1. 创建100个整数的有效负载并等待任何线程可用

    2. 当它从一个线程获得信号时,它会解锁有效载荷以进行复制并继续创建另一个有效载荷

  • 工作线程

    1. 创建它时可以自己进行数据处理并发送可用的信号

    2. 尝试从主线程锁定数据有效负载并将其复制到本地阵列 (在这里观察错误 - 无法正确访问数据)

    3. 关闭可用的标志 (无法关闭可用状态)

    4. 通过本地副本保持处理数据

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>

#define WORKERS 2
#define ARRAY_ELEMENTS 100
#define MAX 1000


pthread_mutex_t mutex_bucket1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_signal = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_go = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_busy = PTHREAD_COND_INITIALIZER;

static int value = 0;
bool available = false;

void *worker_thread(void *pbucket)
{
    sleep(5);
    while(1)
    {
        unsigned int count = 0;
        int local_array[ARRAY_ELEMENTS];
        int *ptbucket = (int*)pbucket;
        setbuf(stdout, NULL);

        pthread_mutex_lock(&mutex_signal);
        printf(" --------------   \n chainging state to available \n --------- ");
        available = true;
        printf(" --------------   \n from thread sending go signal \n --------- ");
        pthread_cond_signal(&cond_go);
        pthread_mutex_unlock(&mutex_signal);


        pthread_mutex_lock(&mutex_bucket1);
        printf(" --------------   \n data part locked in thread for copying \n --------- ");
        while(count < ARRAY_ELEMENTS)
        {
            printf(" %d - \n", ptbucket[count]);  /***incorrect values***/
            local_array[count] = ptbucket[count];
            count++;
        }
        pthread_mutex_unlock(&mutex_bucket1);

        /*Never able to acquire mutex_signal and change state to not available*/   **BUG**
        pthread_mutex_lock(&mutex_signal);
        printf(" --------------   \n chainging state to not available \n --------- ");
        available = false;
        pthread_mutex_unlock(&mutex_signal);

        count = 0;

        while(count < ARRAY_ELEMENTS)
        {
            printf(" %d - \n", local_array[count]);
            count++;
        }

        printf(" --------------   \n about to sleep for 5secs \n --------- ");
        sleep(5);
    }
}

int main(void)
{
    pthread_t thread_id[WORKERS];

    unsigned int* pbucket1 = (int*) malloc(sizeof(int) * ARRAY_ELEMENTS);

    unsigned int* pbucket;

    for(int i = 0; i < WORKERS - 1; i++)
    {
        pthread_create(&thread_id[i], NULL, worker_thread, (void *) pbucket);
    }

    for(int i = 0; i < MAX; i++)
    {
        unsigned int count = 0;

        pbucket = pbucket1;

        // Make the payload ready
        pthread_mutex_lock(&mutex_bucket1);

        printf(" -------------- creating data payload --------- \n");

        while(count < ARRAY_ELEMENTS)
        {
            pbucket1[count] = i;
            i++;
            count++;
        }

        printf(" --------------   \n waiting for go signal \n --------- ");

        while(!available)
        {
            pthread_cond_wait(&cond_go, &mutex_signal);
        }

        pthread_mutex_unlock(&mutex_bucket1);

        /*I believe after we unlock variable "available" can be mutexed
          again by other thread but seems thinking is flawed */

        printf(" --------------   \n Main thread sleep for 3 seconds  \n --------- ");
        sleep(3);
    }

    for(int i = 0; i < WORKERS; i++)
    {
        pthread_join(thread_id[i], NULL);
    }

    return 0;
}

1 个答案:

答案 0 :(得分:2)

我认为你的一些想法是倒退的;它不应该是等待的主要上下文,它应该是等待数据的工作线程...

主线程的工作应该是保持填充有效负载并一次唤醒一个线程来处理它。

所以,我认为这里的一些潦草的代码更加明智:

/**
    file: answer.c
    compile: gcc -o answer answer.c -pthread
    usage: answer [numThreads] [numElements]
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define STATE_WAIT    1
#define STATE_READY   2

void *routine(void*);

typedef struct _shared_t {
    pthread_mutex_t     m;
    pthread_cond_t      c;
    unsigned char       state;
    int                 *payload;
    size_t              numElements;
    pthread_t           *threads;
    size_t              numThreads;
} shared_t;

static inline void shared_init(shared_t *shared, size_t numThreads, size_t numElements) {
    memset(shared, 0, sizeof(shared_t));

    pthread_mutex_init(&shared->m, NULL);
    pthread_cond_init(&shared->c, NULL);

    shared->state = STATE_WAIT;

    shared->numThreads = numThreads;
    shared->numElements = numElements;

    {
        int it = 0;

        shared->threads = (pthread_t*) calloc(shared->numThreads, sizeof(pthread_t));

        while (it < shared->numThreads) {
            if (pthread_create(&shared->threads[it], NULL, routine, shared) != 0) {
                break;
            }
            it++;
        }
    }
}

static inline void shared_populate(shared_t *shared) {
    if (pthread_mutex_lock(&shared->m) != 0) {
        return;
    }

    shared->payload = (int*) calloc(shared->numElements, sizeof(int));  

    {
        int it = 0,
             end = shared->numElements;

        while (it < end) {
            shared->payload[it] = rand();

            it++;
        }
    }

    shared->state = STATE_READY;

    pthread_cond_signal(&shared->c);

    pthread_mutex_unlock(&shared->m);
}

static inline void shared_cleanup(shared_t *shared) {
    int it = 0,
         end = shared->numThreads;

    while (it < end) {
        pthread_join(shared->threads[it], NULL);
    }

    pthread_mutex_destroy(&shared->m);
    pthread_cond_destroy(&shared->c);

    free(shared->threads);
}

void* routine(void *arg) {
    shared_t *shared = (shared_t*) arg;
    int *payload;

    do {
        if (pthread_mutex_lock(&shared->m) != 0) {
            break;
        }

        while (shared->state == STATE_WAIT) {
            pthread_cond_wait(&shared->c, &shared->m);
        }

        payload = shared->payload;

        shared->state = STATE_WAIT;

        pthread_mutex_unlock(&shared->m);

        if (payload) {
            int it = 0,
                 end = shared->numElements;

            while (it < end) {
                printf("Thread #%ld got payload %p(%d)=%d\n", 
                    pthread_self(), payload, it, payload[it]);
                it++;
            }

            free(payload);
        }
    } while(1);

    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    shared_t shared;

    int numThreads = argc > 1 ? atoi(argv[1]) : 1;
    int numElements   = argc > 2 ? atoi(argv[2]) : 100;

    shared_init(&shared, numThreads, numElements);

    do {
        shared_populate(&shared);
    } while (1);

    shared_cleanup(&shared);

    return 0;
}

显然,上面的代码不能容忍错误,并且不容易干净地关闭......仅仅是它的例子。

让我们先看一下main,以便了解主程序的流程:

int main(int argc, char *argv[]) {
    shared_t shared;

    int numThreads = argc > 1 ? atoi(argv[1]) : 1;
    int numElements   = argc > 2 ? atoi(argv[2]) : 100;

    shared_init(&shared, numThreads, numElements);

    do {
        shared_populate(&shared);
    } while (1);

    shared_cleanup(&shared);

    return 0;
}

它在堆栈上保留shared_t

typedef struct _shared_t {
    pthread_mutex_t     m;
    pthread_cond_t      c;
    unsigned char       state;
    int                 *payload;
    size_t              numElements;
    pthread_t           *threads;
    size_t              numThreads;
} shared_t;

同步需要大部分自解释,互斥,条件和状态。

首先,必须使用提供的选项使用互斥锁,条件,状态和线程初始化shared_t

static inline void shared_init(shared_t *shared, size_t numThreads, size_t numElements) {
    memset(shared, 0, sizeof(shared_t));

    pthread_mutex_init(&shared->m, NULL);
    pthread_cond_init(&shared->c, NULL);

    shared->state = STATE_WAIT;

    shared->numThreads = numThreads;
    shared->numElements = numElements;

    {
        int it = 0;

        shared->threads = (pthread_t*) calloc(shared->numThreads, sizeof(pthread_t));

        while (it < shared->numThreads) {
            if (pthread_create(&shared->threads[it], NULL, routine, shared) != 0) {
                break;
            }
            it++;
        }
    }
}

当这个例程创建工作线程时,它们将被强制进入等待状态。

在将有效负载设置为某些随机数后,第一次调用循环中的shared_populate会唤醒第一个线程:

static inline void shared_populate(shared_t *shared) {
    if (pthread_mutex_lock(&shared->m) != 0) {
        return;
    }

    shared->payload = (int*) calloc(shared->numElements, sizeof(int));  

    {
        int it = 0,
             end = shared->numElements;

        while (it < end) {
            shared->payload[it] = rand();

            it++;
        }
    }

    shared->state = STATE_READY;

    pthread_cond_signal(&shared->c);

    pthread_mutex_unlock(&shared->m);
}

请注意使用pthread_cond_signal而不是pthread_cond_broadcast,因为我们只想唤醒第一个帖子。

void* routine(void *arg) {
    shared_t *shared = (shared_t*) arg;
    int *payload;

    do {
        if (pthread_mutex_lock(&shared->m) != 0) {
            break;
        }

        while (shared->state == STATE_WAIT) {
            pthread_cond_wait(&shared->c, &shared->m);
        }

        payload = shared->payload;

        shared->state = STATE_WAIT;

        pthread_mutex_unlock(&shared->m);

        if (payload) {
            int it = 0,
                 end = shared->numElements;

            while (it < end) {
                printf("Thread #%ld got payload %p(%d)=%d\n", 
                    pthread_self(), payload, it, payload[it]);
                it++;
            }

            free(payload);
        }
    } while(1);

    pthread_exit(NULL);
}

所以我们在调用routine时在pthread_cond_wait中醒来,状态已经改变,所以我们突破循环,将指针保存到有效负载,将状态重置为WAIT,并释放互斥锁。

此时main可以重新填充有效负载并唤醒下一个线程,同时当前工作线程可以处理,然后释放有效负载。

一些建议:

  • 始终使用尽可能少的互斥和条件变量(KISS)
  • 研究条件变量的原子性
  • 始终遵循有关获取和释放互斥锁以及条件变量信号的基本规则:
    • 如果您将其锁定,请将其解锁。
    • 只有等待 的东西:绝对需要谓词等待循环。

如果你无法重现我所做的事情,那就拿下代码并尝试扩展它;您需要做的第一件事就是能够优雅地关闭流程(输入shared_cleanup),也许您需要一个可变大小的有效负载,或原始问题中未提及的其他一些要求。

注意关于printf ...附加到流不保证是原子的,它发生在* nix上的大部分时间它是......因为我们只是在做节目和告诉我们,我们不需要关心它......通常,不要依赖原子性进行任何流操作...