c++oding="utf-8" ?>
c++多线程同步通过多种机制确保线程安全;1.互斥锁(mutex)用于保护共享资源,如代码中使用mtx.lock()和mtx.unlock()控制counter访问;2.条件变量(condition variable)用于线程等待特定条件,如cv.wait()和cv.notify_one()配合unique_lock实现线程通信;3.原子操作(atomic operations)提供轻量级同步,如std::atomic保证counter++的原子性;4.读写锁(read-write lock)允许多个线程同时读取,如std::shared_mutex配合shared_lock和unique_lock实现读写控制;5.信号量(semaphore)控制资源访问数量,如std::counting_semaphore管理最多三个并发线程。选择时应根据场景:互斥锁适合保护共享数据,条件变量适合等待条件触发,原子操作适合简单计数器,读写锁适合读多写少,信号量适合资源池管理。避免死锁的方法包括避免嵌套锁、使用超时锁、减小锁粒度、资源排序等。c++11后新增了recursive_mutex、timed_mutex、future/promise等工具提升并发编程能力。原子操作适用于简单操作,互斥锁则更适合复杂资源保护。

C++多线程同步,简单来说,就是让多个线程能够安全地共享资源,避免出现数据竞争和死锁等问题。要做到这一点,你需要用到一些同步机制,比如互斥锁、条件变量、原子操作等等。

互斥锁(Mutex) 最常用的同步机制之一。想象一下,你和你的朋友想同时用一支笔,但笔只有一个,这时候就需要互斥锁。

#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义一个互斥锁
int counter = 0;
void increment() {
for (int i = 0; i <p>这段代码里,<code>mtx.lock()</code> 就像是你拿起了笔,<code>mtx.unlock()</code> 就像是你用完放下了笔。只有拿到锁的线程才能访问 <code>counter</code> 变量。</p>
<img src="https://img.php.cn/upload/article/001/431/639/175022227426917.png?x-oss-process=image/resize,p_40" alt="C++怎么使用多线程同步 C++多线程同步的几种机制对比"><p>条件变量(Condition Variable)</p>
<p>互斥锁可以保证资源的安全访问,但如果线程需要等待某个特定条件满足才能继续执行,互斥锁就不够用了。这时候就需要条件变量。</p>
<pre class="brush:cpp;toolbar:false;">#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker_thread() {
std::unique_lock<:mutex> lock(mtx); // 自动解锁的互斥锁
cv.wait(lock, []{ return ready; }); // 等待条件满足
std::cout lock(mtx);
ready = true;
}
cv.notify_one(); // 通知一个等待的线程
}
int main() {
std::thread worker(worker_thread);
std::thread signaler(signal_ready);
worker.join();
signaler.join();
return 0;
}</:mutex></condition_variable></mutex></thread></iostream>
cv.wait(lock, []{ return ready; }) 让线程进入等待状态,直到 ready 变为 true。cv.notify_one() 用于唤醒一个等待的线程。 std::unique_lock 是一个RAII风格的锁,离开作用域会自动解锁,避免忘记解锁导致死锁。
原子操作(Atomic Operations)
原子操作是一种更轻量级的同步机制,适用于简单的计数器或者标志位。原子操作保证操作的原子性,即不可分割。
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i <p><code>std::atomic<int> counter(0)</int></code> 定义了一个原子整数。<code>counter++</code> 是一个原子操作,保证了<a style="color:#f60; text-decoration:underline;" title="并发访问" href="https://m.php.cn/zt/35877.html" target="_blank">并发访问</a>的安全性。</p>
<p>读写锁(Read-Write Lock)</p>
<p>读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这种锁适用于读多写少的场景。C++<a style="color:#f60; text-decoration:underline;" title="标准库" href="https://m.php.cn/zt/74427.html" target="_blank">标准库</a>并没有直接提供读写锁,但你可以使用第三方库,例如 Boost。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<pre class="brush:cpp;toolbar:false;">#include <iostream>
#include <thread>
#include <shared_mutex> // C++17引入
std::shared_mutex rw_mutex;
int data = 0;
void reader() {
for (int i = 0; i lock(rw_mutex); // 共享锁
std::cout lock(rw_mutex); // 独占锁
data++;
std::cout <p><code>std::shared_lock</code> 用于获取共享锁,允许多个线程同时读取数据。<code>std::unique_lock</code> 用于获取独占锁,只允许一个线程写入数据。C++17 引入了 <code>std::shared_mutex</code>,简化了读写锁的使用。</p>
<p>信号量(Semaphore)</p>
<p>信号量是一种更通用的同步机制,可以控制对有限资源的访问。</p>
<pre class="brush:cpp;toolbar:false;">#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore semaphore(3); // 允许最多3个线程同时访问
void worker(int id) {
semaphore.acquire(); // 获取信号量
std::cout <p><code>std::counting_semaphore semaphore(3)</code> 定义了一个初始值为 3 的信号量。<code>semaphore.acquire()</code> 用于获取信号量,如果信号量的值为 0,线程将进入等待状态。<code>semaphore.release()</code> 用于释放信号量,使信号量的值加 1。</p>
<h3>如何选择合适的同步机制?</h3>
<p>选择合适的同步机制取决于你的具体需求。</p>
<ul>
<li>
<strong>互斥锁</strong>:适用于保护共享资源,确保同一时间只有一个线程可以访问。</li>
<li>
<strong>条件变量</strong>:适用于线程需要等待特定条件满足才能继续执行的场景。</li>
<li>
<strong>原子操作</strong>:适用于简单的计数器或者标志位,性能较高。</li>
<li>
<strong>读写锁</strong>:适用于读多写少的场景,可以提高并发性能。</li>
<li>
<strong>信号量</strong>:适用于控制对有限资源的访问。</li>
</ul>
<h3>多线程同步可能遇到的问题有哪些?</h3>
<p>多线程同步虽然能解决并发访问的问题,但也可能引入一些新的问题。</p>
<ul>
<li>
<strong>死锁</strong>:当多个线程互相等待对方释放资源时,就会发生死锁。</li>
<li>
<strong>活锁</strong>:线程不断重试一个操作,但由于其他线程的干扰,始终无法成功。</li>
<li>
<strong>饥饿</strong>:某个线程长时间无法获得所需的资源,导致无法执行。</li>
<li>
<strong>优先级反转</strong>:高优先级线程等待低优先级线程释放资源,导致高优先级线程的执行被延迟。</li>
</ul>
<h3>如何避免死锁?</h3>
<p>死锁是多线程编程中最常见的问题之一,以下是一些避免死锁的常用方法:</p>
<ul>
<li>
<strong>避免嵌套锁</strong>:尽量避免在一个锁的保护范围内再请求另一个锁。如果必须使用嵌套锁,确保所有线程以相同的顺序获取锁。</li>
<li>
<strong>使用超时锁</strong>:<code>try_lock</code> 可以尝试获取锁,如果一段时间内无法获取,则返回失败,避免永久等待。</li>
<li>
<strong>锁的粒度</strong>:减小锁的粒度,尽量只保护需要同步的最小代码块,减少锁的竞争。</li>
<li>
<strong>资源排序</strong>:对所有需要锁定的资源进行排序,所有线程按照相同的顺序获取锁。</li>
</ul>
<h3>C++11 之后有哪些新的同步工具?</h3>
<p>C++11 引入了许多新的同步工具,例如:</p>
<ul>
<li>
<code>std::mutex</code>:互斥锁。</li>
<li>
<code>std::recursive_mutex</code>:递归锁,允许同一个线程多次获取同一个锁。</li>
<li>
<code>std::timed_mutex</code>:定时锁,可以设置获取锁的超时时间。</li>
<li>
<code>std::condition_variable</code>:条件变量。</li>
<li>
<code>std::atomic</code>:原子操作。</li>
<li>
<code>std::future</code> 和 <code>std::promise</code>:用于异步编程,可以方便地获取异步操作的结果。</li>
<li>
<code>std::shared_mutex</code> (C++17):读写锁。</li>
<li>
<code>std::counting_semaphore</code> (C++20):信号量。</li>
</ul>
<h3>什么时候应该使用原子操作而不是互斥锁?</h3>
<p>原子操作通常比互斥锁更轻量级,性能更高,但原子操作只能用于简单的操作,例如计数器或者标志位的更新。如果需要保护复杂的共享资源,或者需要执行多个操作的原子性,应该使用互斥锁。</p>
<p>总的来说,选择合适的同步机制,并正确地使用它们,是编写高效、安全的并发程序的关键。</p></semaphore></thread></iostream>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










