Skip to content

Commit d4af5ed

Browse files
committed
更新视频代码以及CMake设置,引入三方库设置。
教案还未完成,先编写测试示例,后台播放音乐。
1 parent 79952e4 commit d4af5ed

10 files changed

+312
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ node_modules/
4141
.vs/
4242
x64/
4343
out/
44+
bin/
4445
*.ilk
4546
*.pdb
4647

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <mutex>
4+
5+
struct X {
6+
X(int v) { // 主要防止有人认为构造函数、析构函数啊,是线程安全的
7+
std::cout << v << " 🤣\n";
8+
}
9+
};
10+
11+
void f() {
12+
X* p = new X{ 1 }; // 存在数据竞争
13+
delete p;
14+
}
15+
16+
int main()
17+
{
18+
for (int i = 0; i < 10; ++i) {
19+
std::thread t{ f };
20+
std::thread t2{ f };
21+
t.join();
22+
t2.join();
23+
}
24+
25+
// C++ 保证的是内存的申请和释放 这种全局状态 是线程安全的
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <iostream>
2+
#include <thread>
3+
4+
int global_counter = 0;
5+
__declspec(thread) int thread_local_counter = 0;
6+
7+
void print_counters() {
8+
std::cout << "global:" << global_counter++ << '\n';
9+
std::cout << "thread_local:" << thread_local_counter++ << '\n';
10+
}
11+
12+
int main() {
13+
std::thread{ print_counters }.join();
14+
std::thread{ print_counters }.join();
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <iostream>
2+
#include <thread>
3+
4+
thread_local int n = (std::puts("thread_local init"), 0);
5+
6+
void f(){
7+
(void)n; // 防止 gcc 与 clang 优化
8+
std::puts("f");
9+
}
10+
11+
void f2(){
12+
thread_local static int n = (std::puts("f2 init"), 0);
13+
}
14+
15+
int main(){
16+
(void)n; // 防止 gcc 与 clang 优化
17+
std::cout << "main\n";
18+
std::thread{ f }.join();
19+
f2();
20+
f2();
21+
f2();
22+
}
23+
24+
// gcc 与 clang 存在优化,会出现与 msvc 不同的结果,它们直接将线程变量优化掉了
25+
// 这应该视作 bug。
26+
// 视频中想到 std::async 是下一章的内容跳过了(想到的是 msvc 的一个问题),忘记了 gcc 与 clang 此处也存在问题。
27+
// https://godbolt.org/z/qa6YfMqP7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <condition_variable>
4+
#include <mutex>
5+
#include <chrono>
6+
using namespace std::chrono_literals;
7+
8+
std::mutex mtx; // 互斥量
9+
std::condition_variable_any cv; // 条件变量
10+
bool arrived = false;
11+
12+
void wait_for_arrival() {
13+
std::unique_lock<std::mutex> lck(mtx); // 上锁
14+
cv.wait(lck, [] { return arrived; }); // 等待 arrived 变为 true 会解锁的 再次上锁
15+
std::cout << "到达目的地,可以下车了!" << std::endl;
16+
}
17+
18+
void simulate_arrival() {
19+
std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟地铁到站,假设5秒后到达目的地
20+
{
21+
std::lock_guard<std::mutex> lck(mtx);
22+
arrived = true; // 设置条件变量为 true,表示到达目的地
23+
}
24+
cv.notify_one(); // 通知等待的线程
25+
}
26+
27+
int main(){
28+
std::thread t{ wait_for_arrival };
29+
std::thread t2{ simulate_arrival };
30+
t.join();
31+
t2.join();
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <condition_variable>
4+
#include <mutex>
5+
#include <chrono>
6+
#include <queue>
7+
using namespace std::chrono_literals;
8+
9+
template<typename T>
10+
class threadsafe_queue {
11+
mutable std::mutex m; // M&M 原则 互斥量,用于保护队列操作的独占访问
12+
std::condition_variable data_cond; // 条件变量,用于在队列为空时等待
13+
std::queue<T> data_queue; // 实际存储数据的队列
14+
public:
15+
threadsafe_queue() {}
16+
17+
void push(T new_value) {
18+
{
19+
std::lock_guard<std::mutex> lk{ m };
20+
std::cout << "push:" << new_value << std::endl;
21+
data_queue.push(new_value);
22+
}
23+
data_cond.notify_one();
24+
}
25+
// 从队列中弹出元素(阻塞直到队列不为空)
26+
void pop(T& value) {
27+
std::unique_lock<std::mutex> lk{ m };
28+
data_cond.wait(lk, [this] {return !data_queue.empty(); }); // 解除阻塞 重新获取锁 lock
29+
value = data_queue.front();
30+
std::cout << "pop:" << value << std::endl;
31+
data_queue.pop();
32+
}
33+
// 从队列中弹出元素(阻塞直到队列不为空),并返回一个指向弹出元素的 shared_ptr
34+
std::shared_ptr<T> pop() {
35+
std::unique_lock<std::mutex> lk{ m };
36+
data_cond.wait(lk, [this] {return !data_queue.empty(); });
37+
std::shared_ptr<T>res{ std::make_shared<T>(data_queue.front()) };
38+
data_queue.pop();
39+
return res;
40+
}
41+
bool empty()const {
42+
std::lock_guard<std::mutex> lk(m);
43+
return data_queue.empty();
44+
}
45+
};
46+
47+
void producer(threadsafe_queue<int>& q) {
48+
for (int i = 0; i < 5; ++i) {
49+
q.push(i);
50+
}
51+
}
52+
void consumer(threadsafe_queue<int>& q) {
53+
for (int i = 0; i < 5; ++i) {
54+
int value{};
55+
q.pop(value);
56+
}
57+
}
58+
59+
int main() {
60+
threadsafe_queue<int> q;
61+
62+
std::thread producer_thread(producer, std::ref(q));
63+
std::thread consumer_thread(consumer, std::ref(q));
64+
65+
producer_thread.join();
66+
consumer_thread.join();
67+
}

code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt

+12-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,19 @@ set(CMAKE_CXX_STANDARD 17)
77
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
88

99
if(MSVC)
10-
add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo" "/Od")
10+
add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo")
1111
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
1212
add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8")
1313
endif()
1414

15-
add_executable(ModernCpp-ConcurrentProgramming-Tutorial "20recursive_mutex.cpp")
15+
add_executable(${PROJECT_NAME} "25线程安全的队列.cpp")
16+
17+
18+
# 设置 SFML 的 CMake 路径
19+
set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML")
20+
21+
# 查找 SFML 库并设置链接选项
22+
find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED)
23+
24+
# 链接 SFML 库到项目
25+
target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include "test/AduioPlayer.h"
2+
3+
AudioPlayer audioPlayer;
4+
5+
int main(){
6+
audioPlayer.addAudioPath(AudioPlayer::soundResources[4]);
7+
audioPlayer.addAudioPath(AudioPlayer::soundResources[5]);
8+
audioPlayer.addAudioPath(AudioPlayer::soundResources[6]);
9+
audioPlayer.addAudioPath(AudioPlayer::soundResources[7]);
10+
11+
std::thread t{ []{
12+
std::this_thread::sleep_for(1s);
13+
audioPlayer.addAudioPath(AudioPlayer::soundResources[1]);
14+
} };
15+
std::thread t2{ []{
16+
audioPlayer.addAudioPath(AudioPlayer::soundResources[0]);
17+
} };
18+
19+
std::cout << "\n";
20+
21+
t.join();
22+
t2.join();
23+
24+
std::cout << "end\n";
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#ifndef AUDIOPLAYER_H
2+
#define AUDIOPLAYER_H
3+
4+
#include <iostream>
5+
#include <thread>
6+
#include <mutex>
7+
#include <condition_variable>
8+
#include <queue>
9+
#include <string>
10+
#include <atomic>
11+
#include <array>
12+
#include <SFML/Audio.hpp>
13+
using namespace std::chrono_literals;
14+
15+
class AudioPlayer {
16+
public:
17+
AudioPlayer() : stop{ false }, player_thread{ &AudioPlayer::playMusic, this }
18+
{}
19+
20+
~AudioPlayer() {
21+
// 等待队列中所有音乐播放完毕
22+
while (!audio_queue.empty()) {
23+
std::this_thread::sleep_for(50ms);
24+
}
25+
stop = true;
26+
cond.notify_all();
27+
if (player_thread.joinable()) {
28+
player_thread.join();
29+
}
30+
}
31+
32+
void addAudioPath(const std::string& path) {
33+
std::lock_guard<std::mutex> lock{ mtx };
34+
audio_queue.push(path);
35+
cond.notify_one(); // 通知线程新的音频
36+
}
37+
38+
private:
39+
void playMusic() {
40+
while (!stop) {
41+
std::string path;
42+
{
43+
std::unique_lock<std::mutex> lock{ mtx };
44+
cond.wait(lock, [this] { return !audio_queue.empty() || stop; });
45+
46+
if (audio_queue.empty()) return; // 防止在对象为空时析构出错
47+
48+
path = audio_queue.front();
49+
audio_queue.pop();
50+
}
51+
52+
if (!music.openFromFile(path)) {
53+
std::cerr << "无法加载音频文件: " << path << std::endl;
54+
continue; // 继续播放下一个音频
55+
}
56+
57+
music.play();
58+
59+
// 等待音频播放完毕
60+
while (music.getStatus() == sf::SoundSource::Playing) {
61+
sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等占用CPU
62+
}
63+
}
64+
}
65+
66+
std::atomic<bool> stop;
67+
std::thread player_thread;
68+
std::mutex mtx;
69+
std::condition_variable cond;
70+
std::queue<std::string> audio_queue;
71+
sf::Music music;
72+
73+
public:
74+
static constexpr std::array soundResources{
75+
"./sound/01初始化失败.ogg",
76+
"./sound/02初始化成功.ogg",
77+
"./sound/03试剂不足,请添加.ogg",
78+
"./sound/04试剂已失效,请更新.ogg",
79+
"./sound/05清洗液不足,请添加.ogg",
80+
"./sound/06废液桶即将装满,请及时清空.ogg",
81+
"./sound/07废料箱即将装满,请及时清空.ogg",
82+
"./sound/08激发液A液不足,请添加.ogg",
83+
"./sound/09激发液B液不足,请添加.ogg",
84+
"./sound/10反应杯不足,请添加.ogg",
85+
"./sound/11检测全部完成.ogg"
86+
};
87+
enum SoundIndex {
88+
InitializationFailed,
89+
InitializationSuccessful,
90+
ReagentInsufficient,
91+
ReagentExpired,
92+
CleaningAgentInsufficient,
93+
WasteBinAlmostFull,
94+
WasteContainerAlmostFull,
95+
LiquidAInsufficient,
96+
LiquidBInsufficient,
97+
ReactionCupInsufficient,
98+
DetectionCompleted,
99+
SoundCount // 总音频数量,用于计数
100+
};
101+
};
102+
103+
#endif // AUDIOPLAYER_H

md/04同步操作.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 同步操作
22

3-
"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在多线程编程中,各个任务通常需要通过同步操作进行相互**协调和等待**,以确保数据的**一致性****正确性**
3+
"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在多线程编程中,各个任务通常需要通过**同步设施**进行相互**协调和等待**,以确保数据的**一致性****正确性**
44

55
本章的主要内容有:
66

@@ -146,7 +146,7 @@ void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) {
146146
147147
## 线程安全的队列
148148
149-
在本节中,我们介绍了一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点:
149+
在本节中,我们介将绍一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点:
150150
151151
1. 当执行 `push` 操作时,需要确保没有其他线程正在执行 `push` 或 `pop` 操作;同样,在执行 `pop` 操作时,也需要确保没有其他线程正在执行 `push` 或 `pop` 操作。
152152
@@ -286,6 +286,8 @@ Consumer 线程弹出元素 4:
286286

287287
到此,也就可以了。
288288

289+
## 使用条件变量实现后台音乐播放
290+
289291
## 使用 `future`
290292

291293
举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习[现代 C++ 模板教程](https://github.com/Mq-b/Modern-Cpp-templates-tutorial)、观看 [mq白](https://space.bilibili.com/1292761396) 的视频教程、玩手机等。不过,你始终在等待一件事情:***车到站***

0 commit comments

Comments
 (0)