简体   繁体   English

这是实现没有互斥对象的异步消息队列的安全方法吗?

[英]Is this a safe way to implement an asynchronous message queue without mutexes?

Below is how I implemented an asynchronous message queue with simple checks using while loops. 下面是我使用while循环通过简单检查实现异步消息队列的方法。 I think this is a better way than using mutexes if it works okay. 我认为,如果可以的话,这比使用互斥锁更好。 It seems to work okay by running some tests in my machine, but I am really not sure about whether this is safe because I have not much experience in programming asynchronous systems for multiple threads/processes. 通过在机器上运行一些测试似乎还可以,但是我真的不确定这样做是否安全,因为我没有为多个线程/进程编写异步系统的经验。 Is my work below safely preventing race conditions? 我的工作低于安全范围吗? or will it crash with some heavier loads or in any other condition? 还是在一些较重的负载或其他任何条件下会崩溃?

typedef struct MessageQueueElement {
    Message message;
    struct MessageQueueElement *next;
} MessageQueueElement;

typedef struct MessageQueue { //singly-linked list as a queue
    MessageQueueElement *first;
    MessageQueueElement *last;
    bool sending;
} MessageQueue;

void createMessageQueue(MessageQueue *this) {
    this->first = malloc(sizeof(MessageQueueElement));
    this->last = this->first;
    this->sending = false;
}

void sendMessage(MessageQueue *this, Message *message) {
    while (this->sending);
    //do nothing while this function is called from another thread

    this->sending = true;
    this->last->message = *message;
    this->last = this->last->next = malloc(sizeof(MessageQueueElement));
    //add a message to the queue

    this->sending = false;
}

int waitMessage(MessageQueue *this, int (*readMessage)(unsigned, unsigned, void *)) {
    while (this->first == this->last);
    //do nothing while the queue is empty

    int n = readMessage(this->first->message.type, this->first->message.code, this->first->message.data);
    MessageQueueElement *temp = this->first;
    this->first = this->first->next;
    free(temp);
    return n;
}

See below for the whole context and some test code. 有关整个上下文和一些测试代码,请参见下文。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>

#define EXIT_MESSAGE 0
#define THREAD_MESSAGE 1
#define EXIT 0
#define CONTINUE 1

int readMessage(size_t type, size_t code, void *data) {
    if (type == THREAD_MESSAGE) {
        printf("message from thread %d: %s\n", code, (char *)data);
        free(data);
    } else {
        return EXIT;
    }
    return CONTINUE;
}

MessageQueue mq;
int nThreads;
int counter = 0;

void *worker(void *p) {
    double pi = 0.0;
    for (int i = 0; i < 10; i += 1) {
        for (int j = 0; j < 100000; j += 1) {
            double n = i * 100000.0 + j;
            pi += (4.0 / (8.0 * n + 1.0) - 2.0 / (8.0 * n + 4.0) - 1.0 / (8.0 * n + 5.0) - 1.0 / (8.0 * n + 6.0)) / pow(16.0, n);
        }
        char *s = malloc(100);
        sprintf(s, "calculating pi... %d percent complete", (i + 1) * 10);
        sendMessage(&mq, &(Message){.type = THREAD_MESSAGE, .code = (int)p, .data = s});
    }
    char *s = malloc(100);
    sprintf(s, "pi equals %.8f", pi);
    sendMessage(&mq, &(Message){.type = THREAD_MESSAGE, .code = (int)p, .data = s});
    counter += 1;
    if (counter == nThreads) {
        sendMessage(&mq, &(Message){.type = EXIT_MESSAGE});
    }
}


int main(int argc, char **argv) {
    nThreads = atoi(argv[1]);
    createMessageQueue(&mq);

    pthread_t threads[nThreads];
    for (int i = 0; i < nThreads; i += 1) {
        pthread_create(&threads[i], NULL, worker, (void *)i);
    }
    while (waitMessage(&mq, readMessage));
    for (int i = 0; i < nThreads; i += 1) {
        pthread_join(threads[i], NULL);
    }
    return 0;
}

Obviously not Ok. 显然不行。 For starters, if two threads are waiting for the queue to become empty, they are likely to both find out that sending == false at exactly the same time, and will both jump in, and then things go WRONG in a bad way. 对于初学者来说,如果两个线程正在等待队列变空,则它们很可能都发现完全在同一时间发送== false,并且都将跳入,然后事情以错误的方式出错。 That's exactly what mutexes are there for. 这正是互斥的目的。 So this doesn't work. 所以这行不通。

It's also awfully bad form to busy wait on a variable. 忙于等待变量也是一种非常糟糕的形式。 If you have a quad core CPU, there is a good chance that four cores spend 100% of available CPU just waiting for a variable to change. 如果您有四核CPU,则很有可能四个核仅在等待变量更改时花费100%的可用CPU。 Not good. 不好。

And since sending isn't volatile, your compiler will not see the slightest reason to generate code to set it to true, so again this isn't going to work. 而且由于发送不是不稳定的,因此编译器将几乎看不出生成将其设置为true的代码的原因,因此这同样行不通。

To get this working, you would need to do all the things that a mutex does. 要使此工作正常进行,您需要完成互斥锁所做的所有事情。 And you'd have to do everything exactly right. 而且您必须完全正确地做所有事情。 Which is highly dependent on the exact processor that you are using. 这很大程度上取决于您使用的确切处理器。 You need to be aware of cache consistency, ordering of read and write operations, memory barriers and so on, and if you haven't even heard of these, then you have no chance to get it right. 您需要了解高速缓存的一致性,读取和写入操作的顺序,内存障碍等,如果您甚至还没有听说过这些,那么您就没有机会做对了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM