我正在编写一个练习程序来理解 C++ 中的多线程概念。这个程序只是读取一个字符串,从一个线程上的用户输入,然后在另一个线程中处理它。
1 #include <iostream>
2 #include <thread>
3 #include <condition_variable>
4 #include <mutex>
5 #include <queue>
6 #include <string>
7
8 #define __DEBUG
9
10 #ifdef __DEBUG
11 #define PRINT cout << __FUNCTION__ << " --- LINE : " << __LINE__ << '\n'
12 #else
13 #define PRINT
14 #endif
15
16 using namespace std;
17
18 condition_variable g_CondVar;
19 mutex g_Mutex;
20 queue<string> g_Queue;
21
22 void AddTaskToQueue()
23 {
24 string InputStr;
25 while (true)
26 {
27 lock_guard<mutex> LG(g_Mutex);
28 PRINT;
29 cin >> InputStr;
30 PRINT;
31 g_Queue.push(InputStr);
32 PRINT;
33 g_CondVar.notify_one();
34 PRINT;
35 if (InputStr == "Exit")
36 break;
37 this_thread::sleep_for(50ms);
38 }
39 }
40
41 void ProcessQueue()
42 {
43 PRINT;
44 unique_lock<mutex> UL(g_Mutex);
45 PRINT;
46 string ProcessingStr;
47 PRINT;
48 while (true)
49 {
50 PRINT;
51 g_CondVar.wait(UL, [] {return !g_Queue.empty(); });
52 PRINT;
53 ProcessingStr = g_Queue.front();
54 cout << "Processing ----" << ProcessingStr << "----" << '\n';
55 PRINT;
56 g_Queue.pop();
57 PRINT;
58 if (ProcessingStr == "Exit")
59 break;
60 this_thread::sleep_for(50ms);
61 }
62 }
63
64 int main()
65 {
66 thread TReadInput(AddTaskToQueue);
67 thread TProcessQueue(ProcessQueue);
68
69 TReadInput.join();
70 TProcessQueue.join();
71 }
输出如下。
AddTaskToQueue --- LINE : 28
ProcessQueue --- LINE : 43
TestString
AddTaskToQueue --- LINE : 30
AddTaskToQueue --- LINE : 32
AddTaskToQueue --- LINE : 34
AddTaskToQueue --- LINE : 28
我有几个问题我不想自己总结/我无法理解。
notify_one()/notify_all()
是否是一个好习惯,如 Line: 33。TProcessQueue
是否有可能在 TReadInput
之前开始执行?我的意思是问 n
线程的执行顺序是否与它们的实例化方式相同?mutex
上调用 wait
之前是否应该锁定 condition_variable
?我的意思是问下面的代码是否可以?//somecode
unique_lock<mutex> UL(Mutex, defer_lock); //Mutex is not locked here
ConditionVariable.wait(UL, /*somecondition*/);
//someothercode
unique_lock<mutex> UL(g_Mutex, defer_lock);
输出如下,并且在 Line: 38AddTaskToQueue --- LINE : 28
ProcessQueue --- LINE : 43
ProcessQueue --- LINE : 45
ProcessQueue --- LINE : 47
ProcessQueue --- LINE : 50
TestString
AddTaskToQueue --- LINE : 30
AddTaskToQueue --- LINE : 32
AddTaskToQueue --- LINE : 34
ProcessQueue --- LINE : 52
Processing ----TestString----
ProcessQueue --- LINE : 55
ProcessQueue --- LINE : 57
d:\agent\_work\1\s\src\vctools\crt\crtw32\stdcpp\thr\mutex.c(175): unlock of unowned mutex
ProcessQueue --- LINE : 50
为什么会发生这种情况,什么是 unlock of unowned mutex
?
答案 0 :(得分:3)
sleep
持有互斥锁。这意味着不同线程可以拥有互斥锁的唯一时间是在 50 毫秒后的微小时间窗口中,当互斥锁被很快释放和重新获取时。您应该解决此问题,并且可能会看到不同的结果。notify
。这样做的原因是被唤醒的线程可能会立即从那里开始运行,但随后被互斥锁阻塞,需要再次休眠并稍后再次被操作系统唤醒。在您的情况下,这肯定会发生,因为互斥锁再保持 50 毫秒。但这更多的是一种效率优化,而不是一种正确性优化。答案 1 :(得分:2)
至于 unlock
之前的 notify_one
,cppreference 表示如下(强调我的):
通知线程不需要在与等待线程持有的互斥锁相同的互斥锁上持有锁;实际上这样做是一种悲观,因为被通知的线程会立即再次阻塞,等待通知线程释放锁。 然而,一些实现(特别是 pthread 的许多实现)认识到这种情况并避免这种情况“通过将等待线程从条件变量的队列直接转移到通知调用中的互斥体的队列,而不唤醒它。
在需要精确调度事件时可能需要在锁定时通知,例如如果条件满足,等待线程将退出程序,导致通知线程的condition_variable 被破坏。在互斥锁解锁之后但在通知之前的虚假唤醒将导致在销毁的对象上调用通知。
更详细的解释here。