Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d4af5ed

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

10 files changed

+312
-4
lines changed

‎.gitignore

Lines changed: 1 addition & 0 deletions
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

Lines changed: 26 additions & 0 deletions
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+
}
Lines changed: 15 additions & 0 deletions
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+
}
Lines changed: 27 additions & 0 deletions
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
Lines changed: 32 additions & 0 deletions
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+
}
Lines changed: 67 additions & 0 deletions
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

Lines changed: 12 additions & 2 deletions
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)
Lines changed: 25 additions & 0 deletions
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+
}
Lines changed: 103 additions & 0 deletions
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

Lines changed: 4 additions & 2 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /