在多线程中使用链表列表队列

时间:2017-03-11 20:04:41

标签: c++ multithreading linked-list thread-safety openmp

我正在学习C ++中的OpenMP并行处理库。我觉得我掌握了基本概念,并尝试通过实现链表队列来测试我的知识。我想从多个线程中使用队列。

这里的挑战是不要两次使用同一节点。所以我考虑在线程之间共享队列,但是一次只允许一个线程更新(转到队列中的下一个节点)。为此,我可以使用关键或锁定。但是,不使用它们;不知何故,它似​​乎完美无缺。没有发生任何竞争条件。

#include <iostream>
#include <omp.h>
#include <zconf.h>


struct Node {
    int data;
    struct Node* next = NULL;
    Node() {}
    Node(int data) {
        this->data = data;
    }
    Node(int data, Node* node) {
        this->data = data;
        this->next = node;
    }
};

void processNode(Node *pNode);

struct Queue {
    Node *head = NULL, *tail = NULL;

    Queue& add(int data) {
        add(new Node(data));
        return *this;
    }

    void add(Node *node) {
        if (head == NULL) {
            head = node;
            tail = node;
        } else {
            tail->next = node;
            tail = node;
        }
    }

    Node* remove() {
        Node *node;
            node = head;
        if (head != NULL)
            head = head->next;

        return node;
    }

};

int main() {
    srand(12);
    Queue queue;
    for (int i = 0; i < 6; ++i) {
        queue.add(i);
    }

    double timer_started = omp_get_wtime();
    omp_set_num_threads(3);
    #pragma omp parallel
    {
        Node *n;
        while ((n = queue.remove()) != NULL) {
            double started = omp_get_wtime();
            processNode(n);
            double elapsed = omp_get_wtime() - started;
            printf("Thread id: %d data: %d, took: %f \n", omp_get_thread_num(), n->data, elapsed);
        }
    }
    double elapsed = omp_get_wtime() - timer_started;

    std::cout << "end. took " << elapsed << " in total " << std::endl;
    return 0;
}

void processNode(Node *node) {
    int r = rand() % 3 + 1; // between 1 and 3
    sleep(r);
}

输出如下:

Thread id: 0 data: 0, took: 1.000136 
Thread id: 2 data: 2, took: 1.000127 
Thread id: 2 data: 4, took: 1.000208 
Thread id: 1 data: 1, took: 3.001371 
Thread id: 0 data: 3, took: 2.001041 
Thread id: 2 data: 5, took: 2.004960 
end. took 4.00583 in total 

我用不同数量的线程运行了很多次。但是,我无法获得任何竞争条件或出错。我原以为两个不同的线程可以调用'remove'并处理一个节点两次。但它没有发生。为什么呢?

https://github.com/muatik/openmp-examples/blob/master/linkedlist/main.cpp

1 个答案:

答案 0 :(得分:2)

首先,您可以永远不会通过测试证明多线程代码是正确的。你的预感,你需要一个锁定/关键部分是正确的。

您的测试在队列中特别容易。以下内容会快速打破您的队列:

Thread 1 processed 11133 nodes.
Thread 0 processed 9039 nodes.

例如显示以下有趣结果:

remove

但是,如果您使用此测试代码创建了一个运行一百万次的队列,那么这并不意味着队列正确实现。

特别是,仅保护@OnClick是不够的,您必须正确保护每个读取和写入队列数据。要了解实现这一目标的难度,请观看excellent talk by Herb Sutter

通常,我建议使用现有的并行数据结构,例如Boost.Lockfree

然而,不幸的是,OpenMP和C++11 lock / atomic primitives don't officially play well together。严格地说,如果您使用OpenMP,您应该坚持使用OpenMP同步原语或使用它们的库。