我知道线程是同时运行的,因此您无法预测执行顺序,但是在提供的代码中,我在运行其他任何线程之前先加入了线程t4
。如果.join()
应该等到线程完成执行后,为什么顺序仍然是随机的?在两个print语句之前连接任何内容将始终导致它们最后打印,而如果我在之后连接所有内容,那么它并不总是紧随其后,为什么?
void task() {
std::cout << "task 1 says Hi\n";
}
void task2() {
std::cout << "task 2 says Hi\n";
}
void task3() {
std::cout << "task 3 says Hi\n";
}
void task4() {
std::cout << "task 4 says Hi\n";
}
int main() {
std::thread t1(task);
std::thread t2(task2);
std::thread t3(task3);
std::thread t4(task4);
t4.join();
std::cout << "main says Hi 1\n";
// synchronize - IMPORTANT!
t2.join();
t3.join();
t1.join();
std::cout << "main says Hi 2" << std::endl;
system("pause");
}
答案 0 :(得分:4)
std::thread::join
仅阻塞当前线程,直到*this
所标识的线程完成执行为止。
因此,这可以确保您在main says Hi 1
之前不会打印task 4 says Hi
,并且在其他三个任务完成执行之前不会打印main says Hi 2
。
四个任务中的printf
语句可以按任何顺序打印,并且来自不同线程的输出也可能交错。
答案 1 :(得分:1)
在创建线程时开始,线程很可能会在加入之前执行它们的代码。您首先创建t1
,t1
最有可能先执行,然后再执行其他所有创建的操作。
join
和t4
不能保证它在t1
或您创建的任何其他线程之前执行。它只是等待t4
的终止。
在Visual Studio中的快速测试显示:
task 4 says Hi
task 2 says Hi
task 3 says Hi
task 2 says Hi
main says Hi 1
main says Hi 2
具有以下代码(摘要):
// ...
std::thread t4(task2);
std::thread t2(task2);
std::thread t3(task3);
std::thread t2(task2);
// ...
答案 2 :(得分:0)
加入线程的终止与加入线程中join()
的结尾与同步。简而言之,这意味着join()
之后在连接线程之后发生的所有事情也将保证在连接线程中发生的所有事情 之后发生。 C ++标准中有几个地方建立了这种类型的强同步(例如,一个内存顺序为std::memory_order_seq_cst
的线程中的原子存储与另一个线程 if上的原子负载同步)。 em>加载的值是存储在第一个线程中的值)。还给出了一些较弱的保证,例如用于内存顺序较弱的原子访问。
您可以将执行结果视为执行由这些同步关系产生的有向无环图的topological sort的可能结果之一。 (如果该图是循环的,则说明存在死锁。)
特别是,如果没有从任何一个到另一个的同步路径存在,则不保证操作的相对顺序。您不能向后移动边缘,但这似乎是您希望事情解决的方式。仅仅因为主线程在线程1之前加入了线程4,并不意味着线程4上的任何事情发生在线程1上的任何事情之前。将需要在DAG中引入其他优势或重新排序操作以使其成立(即,可证明)。例如。您可以从主线程启动线程1,只有在线程4 – then 加入之后,线程1中发生的所有事情都会在线程4中发生的一切之后发生,因为线程创建与-同步。新线程的开始,然后建立一个类似以下内容的传递依赖项:
thread4.join()
同步。thread1
之前(再次,仅是因为它是同一线程)。thread1
上发生的所有操作保持同步。TL; DR:线程同步原则上是一条单向路。就像刷新缓冲区一样(实际上,它可能涉及刷新硬件级别的所谓存储缓冲区):它保证所有先前的写操作都已完成,但不会延迟任何形式的先前写操作。如果需要更严格的保证,则必须使用其他同步方法。