<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path> </svg> <p></p>
讯享网
std::thread 是 C++11 标准引入的一个类,用于在程序中创建和管理线程。
讯享网
或者使用类和静态成员函数、类指针和类成员函数、lambda表达式等。
joinable():用来判断一个线程是否是可连接的(joinable)。它的主要作用是检查当前的线程对象是否仍然拥有有效的线程,并且该线程是否可以调用 join() 或 detach()。
join():意味着主线程会阻塞等待子线程执行完。
detch():意味着让线程后台执行,但是需要确保线程后台执行时,访问的资源不会被释放。
jion() 和 detach() 调用返回后,joinable() 返回 false。
再上一个例子中,创建了两个线程 t4 和 t5 来并发地执行 f4()。两个线程都在修改同一个 index 成员变量,且 index++ 操作不是原子性的。因此,两个线程之间可能会同时读取 index 的值并执行加法操作,导致数据竞争所以不是线程安全的。我们怎么能避免这种情况发生呢?
std::mutex互斥锁
顾名思义,它允许多个线程在访问共享资源时,互相排斥,保证同一时间只有一个线程能访问资源。
它是怎么实现的呢?
lock():加锁,如果该资源被加锁则其他线程不得访问或者加锁。只有该资源不是被锁的状态,其他线程才可以访问或者加锁。同一时刻只有一个线程拥有锁。
try_lock():尝试加锁,是非阻塞的,如果锁不可用,它会立即返回
unlock():解锁
通过加锁和解锁来保证同一时间只有一个线程能访问资源。
讯享网
std::lock_guard互斥锁
std::unique_lock互斥锁
std::unique_lock同std::lock_guard,也基于 RAII(Resource Acquisition Is Initialization)实现的一种简单的锁管理方式。它在创建时自动锁定互斥锁,并在离开作用域时自动解锁,从而避免忘记解锁的问题。而且std::unique_lock还提供了更灵活的控制,可以提前解锁,或者重新加锁。
try_lock():尝试加锁,是非阻塞的,如果锁不可用,它会立即返回false
try_lock_for():会尝试在指定的时间内获取锁。如果在指定的时间内成功获取锁,返回 true;如果超时则返回 false,并且不会阻塞线程。
try_lock_until():会尝试在指定的时间点之前获取锁。如果当前时间在指定的时间点之前可以成功获取锁,则返回 true;如果超过指定时间点则返回 false,且不会阻塞。
讯享网
std::recursive_mutex 互斥递归锁
std::recursive_mutex一种特殊类型的互斥锁,它允许 同一个线程 多次锁定同一个互斥锁,而不会发生死锁。这与 std::mutex 不同,std::mutex 是 不可重入的,即同一个线程如果试图多次锁定同一个互斥锁,就会造成死锁。
允许 同一线程多次加锁。当线程第一次锁定一个 std::recursive_mutex 时,锁计数会增加,线程可以再次加锁同一把互斥锁(递归锁定)。每次加锁时,锁计数增加;每次解锁时,锁计数减少,直到计数为零时,锁才会完全释放。
如果 其他线程 尝试锁定已经被当前线程锁定的 std::recursive_mutex,它们会被阻塞,直到该线程释放锁。
递归锁一般使用在递归函数中
std::condition_variable条件变量
std::condition_variable 是 C++ 标准库提供的一个同步原语,通常与 std::mutex 或 std::unique_lock 配合使用,用于在线程之间进行通知和等待。它允许一个或多个线程等待某个条件发生变化,并在条件满足时被通知并继续执行。
wait():线程调用 wait() 函数并阻塞,直到满足某个条件时,它会被唤醒继续执行。wait() 会自动释放与其关联的互斥锁,并在被唤醒时重新加锁。
notify_one():通知一个正在等待该条件的线程,通常用于单个线程的唤醒。
notify_all():通知所有等待该条件的线程。
常见使用场景
生产者-消费者模型:一个线程生产数据,另一个线程消费数据,消费者线程等待生产者提供数据。
线程池:线程池中的线程等待任务的到来,当任务到来时,线程被唤醒并开始执行任务。
线程间的协调:线程在某个条件发生变化时需要被唤醒,例如等待某个值变成特定状态。
假设有一个生产者线程,它会将数据放入一个共享缓冲区中,消费者线程会等待缓冲区中有数据,然后消费它。std::condition_variable 可以用于实现生产者和消费者之间的同步。
讯享网
虚假唤醒(Spurious Wakeups):即使没有调用 notify_one() 或 notify_all(),线程有时也可能被唤醒。这种情况被称为虚假唤醒。为了避免这种情况,cv.wait() 需要与条件变量的判断语句一起使用,例如:cv.wait(lock, { return buffer.size() < max_buffer_size; })。它会保证即使线程被虚假唤醒,条件仍然成立时才会继续执行。
std::condition_variable 与 std::unique_lock一起使用,因为 std::unique_lock 允许在被阻塞时释放锁,直到条件满足才重新加锁。
什么是死锁呢?
上面总是提到死锁,那是什么是死锁呢?
死锁(Deadlock)发生在两个或更多线程互相等待对方释放资源,导致所有线程都永远无法继续执行。在多线程编程中,死锁常常由于不当的锁定顺序和资源竞争引起。
比如下面这个例子
function1第一次mutex.lock(),后当前线程就是拥有锁的状态,但是执行到function2(),再次尝试加锁但此时锁已经被加上了,所以会阻塞等待解锁,但是只有向下执行才能解锁,所以导致永远的阻塞在了function2的mutex.lock()。
还一种情况,造成环形死锁。
讯享网
原子变量:是由 std::atomic 模板类提供的,它允许你使用原子操作对其进行访问和修改,而不需要锁机制。std::atomic 为数据提供了一种线程安全的访问方式。
原子操作:是指操作在执行过程中不可中断,要么完全执行,要么完全不执行。换句话说,原子操作对外表现为一个不可分割的整体。在多线程环境下,原子操作能够保证不会被其他线程的操作打断,因此是线程安全的。原子操作和原子变量提供了一种线程安全的方式来进行数据修改和同步,而不需要使用传统的锁机制(如 std::mutex)。
内存序:在多线程编程中,操作的顺序有时并不完全符合程序的逻辑顺序。这是因为现代 CPU 可能会对指令进行乱序执行以优化性能。为了确保原子操作的正确性和数据同步,std::atomic 提供了不同的内存顺序选项。
std::memory_order_relaxed:不保证任何同步或顺序,只保证原子操作的原子性。
std::memory_order_consume:保证数据依赖的同步顺序(但在现代编译器中通常等同于 memory_order_acquire)。
std::memory_order_acquire:保证当前操作前的所有操作在内存上发生顺序一致。
std::memory_order_release:保证当前操作后的所有操作在内存上发生顺序一致。
std::memory_order_acq_rel:结合 acquire 和 release 顺序。
std::memory_order_seq_cst:提供最严格的顺序保证,默认的顺序。
std::atomic原子变量
std::atomic 是 C++ 标准库提供的一个模板类,用于声明原子变量。它保证了对该变量的访问是线程安全的,且无需额外的同步原语(如 std::mutex)。

以下atomic 的成员函数都是原子操作
load(): 读取原子变量的值。
store(): 设置原子变量的值。
exchange(): 将原子变量的值替换为给定值,并返回原值。
fetch_add(), fetch_sub(): 原子地执行加法或减法操作。
compare_exchange_weak() 和 compare_exchange_strong(): 比较并交换操作(CAS),原子地检查当前值是否与预期值相等,若相等则交换为新值。
Fence (内存屏障)
内存屏障(也叫内存栅栏)用于控制处理器的指令执行顺序。在多核处理器中,为了提高性能,指令可以被重排序,这意味着某些操作可能在程序中的逻辑顺序之后执行。内存屏障通过禁止或限制某些类型的重排序,保证特定操作在内存中的顺序。
内存屏障不直接对内存中的数据进行操作,而是通过强制执行操作顺序来影响内存的访问。
C++11 提供了 std::atomic_thread_fence,它可以创建内存屏障,用来确保不同线程之间的操作不会乱序执行。
std::atomic_thread_fence
std::atomic_thread_fence 是 C++11 中引入的一个低级原子操作,用于在多线程环境中提供内存屏障(memory barrier)。它不会操作任何变量的值,但可以控制不同线程之间的操作顺序。简单来说,它用来强制执行某些线程之间的操作顺序,确保在指定的屏障之前和之后的操作顺序得到正确的同步。
Acquire Fence (memory_order_acquire):保证屏障前的所有操作在屏障之后的操作之前执行。这是确保从另一个线程读取的某个值后,之后的操作不会被乱序执行。
Release Fence (memory_order_release):确保屏障前的操作在屏障之后的所有操作之前执行。常用于发布操作后确保内存写入顺序。
SeqCst Fence (memory_order_seq_cst):最严格的顺序保证,确保所有线程之间的操作在一个统一的顺序上进行。
Fence-Atomic
Fence-Atomic:先使用内存屏障,再执行原子操作。这可以确保屏障前的操作在屏障后进行的原子操作之前完成。
讯享网
Atomic-Fence
Atomic-Fence:先执行原子操作,再使用内存屏障。这确保了原子操作之后的操作不会被重排到原子操作之前。
Fence-Fence
Fence-Fence:仅使用两个内存屏障,确保某些操作的顺序性。
讯享网
讯享网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/160181.html