【发布时间】:2017-03-11 20:04:41
【问题描述】:
我正在学习 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
我已经用不同数量的线程多次运行它。但是,我无法得到任何比赛条件或错误。我在想两个不同的线程可以调用“删除”并处理单个节点两次。但它没有发生。为什么?
https://github.com/muatik/openmp-examples/blob/master/linkedlist/main.cpp
【问题讨论】:
-
您期待什么结果?在处理队列期间,您似乎没有修改/写回变量。如果您不修改任何内容,那么您应该每次都得到相同的结果(线程激活的顺序除外——您无法控制或预测)。如果您执行某种取决于访问节点的顺序的操作,那么您可能会遇到不同的结果。或者,如果您不使用任何锁,但每个线程都写入同一个变量,您可能会遇到竞争条件。抱歉,我现在想不出任何例子。
-
我认为线程使用线程之间共享的队列。他们调用队列的
remove方法,队列将其头指针更改为下一个节点。我在问,是否有可能两个线程同时调用 remove() 并接收相同的节点? -
是的,这是可能的。没有什么可以阻止两个线程在完成删除之前完成
node = head;。到目前为止,您是否幸运或不幸取决于您如何看待它。尝试更长的队列。 -
您可以只拆分数据并为每个线程提供单独的列表。那么并发调用
remove就不会成为问题了。 -
最简单的方法是在
remove函数顶部的locked with astd::scoped_lock队列中使用std::mutex成员。也许不是最有效的,但很容易。
标签: c++ multithreading linked-list thread-safety openmp