Creating a Blocking Queue
有时这种BlockingQueue 的实现和执行是有效的。有时它会出现段错误。知道为什么吗?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#include <thread>
using std::thread; #include <mutex> using std::mutex; #include <iostream> using std::cout; using std::endl; #include <queue> using std::queue; #include <string> using std::string; using std::to_string; #include <functional> using std::ref; template <typename T> void push(T value) { bool empty() { void fillWorkQueue(BlockingQueue<string>& workQueue) { void doWork(BlockingQueue<string>& workQueue) { void multiThreaded() { int main() { // Multi Threaded |
- 如果它出现段错误,我假设你可以得到它发生的代码行?知道可能会有所帮助…
- 如果你检查 itemQueue 是否为空,然后让其他线程做一些工作,然后 pop() 一个项目会发生什么?
- 这个问题有足够的代码,任何人都可以尝试一下,自己看看问题出在哪里。也没有太多多余的代码,因此它与教科书 SSCCE 相距不远,而且肯定可以回答。
- @Flexo 我很难相信”这个程序的错误在哪里?”可能永远是一个好问题。
- 如果您愿意,很高兴在元数据或聊天中进一步解释我对此的想法,但请避免在评论中聊天。
请看这里:
我从空 std 容器的 front() 中得到什么?
如果你在一个空容器上调用 .front() 会发生不好的事情,最好先检查 .empty()。
试试:
1
2 3 4 5 6 7 8 9 10 11 12 |
T pop() {
this–>mutex_.lock(); T value; if( !this–>queue_.empty() ) { value = this–>queue_.front(); // undefined behavior if queue_ is empty // may segfault, may throw, etc. this–>queue_.pop(); } this–>mutex_.unlock(); return value; } |
注意:由于原子操作对这种队列很重要,我建议更改 API:
1
|
bool pop(T &t); // returns false if there was nothing to read.
|
更好的是,如果您确实在重要的地方使用它,您可能希望在删除之前标记正在使用的项目以防失败。
1
2 3 |
bool peekAndMark(T &t); // allows one”marked” item per thread
void deleteMarked(); // if an item is marked correctly, pops it. void unmark(); // abandons the mark. (rollback) |
- @?€???????±á?¥?μá?– 实际上,我自己只是避开了 std:: 和 C。宁可使用 C /CLI、C#、C、Java,除了 vanilla C 和 STL 之外的任何东西。任何将异常定义为不安全的错误功能的语言都值得在我的书中错过。我的意思是谁实现了一个空队列读取为段错误?!
- 在 c 中以这种方式实现锁定/解锁序列很容易出错(并且可能在其他语言中),因为它不是异常安全的。您可以使用 c 标准提供的 std:lock_guard 习语之一,或者在必要时轻松滚动您自己的习语!
- @ebyrob 我同意 API 更改。这是避免”使用什么作为默认值”问题的一个很好的解决方案。
- @ebyrob this->mutex_.lock()/this->mutex_.unlock(); 你不应该直接这样做!改用适当的范围构造/破坏习语!
- 好的@?€???????±á?¥?μá?–。我同意使用 unique_lock<mutex> lock(this->mutex_) 会产生更易读的代码。
- @?€???????±á?¥?μá?– 不好意思,我得考虑一下。您似乎将这两行代码归功于我,只是因为我将它们保留在我的答案中。我非常清楚地从问题中复制粘贴了它们。作为 C 的一个局外人,我很清楚对 .front() 如何工作的误解。所以,我选择强调我熟悉的陷阱。我对 lock_guard 的了解还不够多,无法发表评论。所以我不会。真的,使用 std::string 唯一可能的例外是内存不足。无论如何,这在大多数简单程序中应该是致命的。
问题应该出在这里:
1
2 3 |
while(!itemQueue.empty()) {
itemQueue.pop(); } |
您在检查剩余值时保留互斥锁,然后释放互斥锁,可能会发生另一个线程执行,发现剩余值并将其弹出。在最坏的情况下,之后不会留下任何项目,并且第一个线程会尝试在没有留下任何元素的情况下弹出。
解决方案是在同一节中的内部队列上进行前/弹出调用,而不是在同一锁定节中检查空,然后将始终定义行为。
另一个建议是在使用互斥锁时使用 std::lock_guard,因为它提高了可读性并确保无论发生什么情况都会释放互斥锁。
考虑到这两个建议,您的 pop 方法可能如下所示:
1
2 3 4 5 6 7 8 9 10 |
T pop() {
std::lock_guard lock(this–>mutex_); //mutex_ is locked T value; if( !this–>queue_.empty() ) { value = this–>queue_.front(); this–>queue_.pop(); } return value; } //mutex_ is freed |
- 1 只要提到一个标准或定制的自动储物柜机制,你的答案应该是完美的,尽管是低质量的问题!
- 如果 itemQueue 为空会发生什么?段错误、异常或未定义的行为。我相信 std::queue.front() 行为是这里的惊喜,而不是线程相互抢占。如果您明确提到它,我就不会为答案而烦恼。
- @ebyrob 我说的是他实现的 pop 方法。
- @Theolodis T value; 认真的吗?好像你复制粘贴了我的代码。
- 你的意思是 std::lock_guard<std::mutex> lock(this->mutex_); 吗?
- @KillzoneKid 取决于您使用的互斥锁类型。
来源:https://www.codenong.com/23661759/