c++的mutex怎么用

c++的mutex是不是要关联某个数据才能上锁?
下图中的互斥到底关联了谁?还是都关联了?

img


下图中第一行的mutex是声明还是定义,这行代码有什么作用

img

不要想复杂了,你的图中仅仅是定义了几个变量,又怎么会有关联呢。
回归语言本质,mutex只是一个对象,我们最终用的是它里面的方法lock和unlock。
只有在lock和unlock之间的代码才是线程保护的,也就是说在lock和unlock之间我们用到了counter,这时mutex和counter才有了联系。
没有关不关联变量的说法。至于你说的后边再加一行int counter2=1;加了对mutex和整个程序没有任何的影响,仅仅是多了一个全局变量而已。
另外回答你的 “如果这个作用域有好几个变量和一个mutex,某个线程先拿到mutex并lock,那么其他线程就无法访问这好几个变量了”,对的,从程序角度上说。但如果考虑cpu乱序,就不准确了,这是题外话了。

#include <iostream>  
#include <thread>  
#include <mutex>  
  
std::mutex mtx;  // 创建一个互斥锁  
int counter = 0;  // 共享数据  
  
void increment() {  
    for (int i = 0; i < 100000; ++i) {  
        mtx.lock();  // 获取互斥锁  
        ++counter;   // 增加共享数据  
        mtx.unlock();  // 释放互斥锁  
    }  
}  
  
int main() {  
    std::thread t1(increment);  
    std::thread t2(increment);  
  
    t1.join();  
    t2.join();  
  
    std::cout << counter << std::endl;  // 输出结果应为200000  
    return 0;  
}

在这个例子中,std::mutex mtx;就是定义了一个全局变量mtx, int counter = 0;就是定义了一个int的全局变量,这时候这两者没有任何关系。
真正用到mutex和counter 产生关系的是这里:

        mtx.lock();  // 获取互斥锁  
        ++counter;   // 增加共享数据  
        mtx.unlock();  // 释放互斥锁  

通过lock和unlock把counter保护起来了,至于mutex在这里只是一个对象,没什么特别,我们只是用了mutex的方法而已。
当然在lock和unlock间我们可以加更多代码,更多的变量。
这里仅仅就是为了线程同步,使得counter 同一时间只能有一个线程访问。

你可以这么理解,如果关联的变量是一个有意义的变量,那么就是关联到数据
也有仅仅为了同步,那么可以关联一个对程序逻辑没有任何作用的变量,那么就是相当于不关联任何变量了。

std::mutex是一个对象,你可以使用它的lock()和unlock()方法来加锁和解锁。在使用std::mutex时,你需要确保每个线程在访问共享数据之前先锁定互斥锁,并在离开该作用域或执行完特定的代码块后解锁互斥锁。
下面是一个简单的示例,展示了如何使用std::mutex来保护共享数据,期望能帮助你理解:

#include <iostream>  
#include <thread>  
#include <mutex>  
  
std::mutex mtx;  // 创建一个互斥锁  
int counter = 0;  // 共享数据  
  
void increment() {  
    for (int i = 0; i < 100000; ++i) {  
        mtx.lock();  // 获取互斥锁  
        ++counter;   // 增加共享数据  
        mtx.unlock();  // 释放互斥锁  
    }  
}  
  
int main() {  
    std::thread t1(increment);  
    std::thread t2(increment);  
  
    t1.join();  
    t2.join();  
  
    std::cout << counter << std::endl;  // 输出结果应为200000  
    return 0;  
}

看下这个文章,里面解释了mutex很清楚的用法。

c++之多线程中“锁”(mutex)的用法_one-莫烦的博客-CSDN博客 1. 锁:mutex锁,是生活中应用十分广泛的一种工具。锁的本质属性是为事物提供“访问保护”,例如:大门上的锁,是为了保护房子免于不速之客的到访;自行车的锁,是为了保护自行车只有owner才可以使用;保险柜上的锁,是为了保护里面的合同和金钱等重要东西……在c++等高级编程语言中,锁也是用来提供“访问保护”的,不过被保护的东西不再是房子、自行车、金钱,而是内存中的各种变量。此外,计算机领域对于“锁”有个响亮的名字——mutex(互斥量),学过操作系统的同学对这个名字肯定很熟悉。Mutex,互斥量,就是互_mutex https://blog.csdn.net/weixin_42127358/article/details/123507748


如果以上回答对您有所帮助,点击一下采纳该答案~谢谢

你可以这么理解,如果关联的变量是一个有意义的变量,那么就是关联到数据

C++的mutex是一种多线程同步机制,用于保证对共享资源的互斥访问。在多线程程序中,多个线程可能会同时访问同一份数据,如果不进行同步处理,就会导致数据的不一致性或者错误的结果。这时候就需要使用mutex来进行同步保护。

mutex有两种类型:互斥锁和递归锁。互斥锁是最常用的一种,可以确保同一时间只有一个线程能够访问临界区。递归锁则允许同一线程多次获得锁,但需要在解锁时相应地进行解锁操作。

使用mutex需要包含头文件,下面我们来详细介绍mutex的使用方法。

  1. 创建mutex

mutex的创建非常简单,可以使用std::mutex对象来创建。

#include <mutex>
std::mutex mtx;

在创建mutex对象时,要注意将其声明为全局对象或者静态对象,确保mutex的生命周期与共享资源的生命周期相同。否则,在释放共享资源之前,mutex已经被销毁,就不能保护共享资源了。

  1. 加锁和解锁

为了保护共享资源,我们需要在访问共享资源之前加锁,在访问完成后解锁。加锁和解锁操作都很简单。

#include <mutex>
std::mutex mtx;
 
void func()
{
    mtx.lock();   // 加锁
 
    // 访问共享资源
 
    mtx.unlock(); // 解锁
}

需要注意的是,加锁和解锁操作必须成对出现,不然会导致死锁或者其他问题。如果多线程程序中的锁操作不匹配,就会出现死锁,导致程序卡死。

  1. RAII

RAII(Resource Acquisition Is Initialization)是一种资源管理技术,即在对象构造时获取资源,对象析构时释放资源。使用RAII可以避免忘记加锁或者解锁,从而导致的问题。

我们可以借助C++标准库中的lock_guard类来实现RAII。lock_guard是一个模板类,构造时会自动加锁,析构时会自动解锁。

#include <mutex>
std::mutex mtx;
 
void func()
{
    std::lock_guard<std::mutex> lock(mtx);  // RAII加锁
 
    // 访问共享资源
}
  1. unique_lock

unique_lock是std::mutex的另一种加锁方式。相比于lock_guard,它更加灵活,支持延迟锁定和条件变量。unique_lock有两个模板参数,第一个是要锁定的mutex,第二个是锁定策略。锁定策略有三种:defer_lock、try_to_lock和adopt_lock。

  • defer_lock:创建一个没有上锁的unique_lock对象,需要手动加锁。
  • try_to_lock:尝试加锁,但如果无法获得锁,则不等待,直接返回。
  • adopt_lock:假设mutex已经被当前线程锁住,unique_lock对象不需要再次加锁。

下面是一个使用defer_lock策略的示例代码:

#include <mutex>
std::mutex mtx;
 
void func()
{
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 创建一个没有上锁的unique_lock对象
 
    // 访问共享资源之前,需要手动加锁
    lock.lock();
 
    // 访问共享资源
 
    lock.unlock();   // 手动解锁
}
  1. 条件变量

条件变量是一种等待通知的机制,被用来协调多个线程之间的操作。条件变量依赖于互斥锁,当某个条件不满足时,线程会进入等待状态,同时解锁互斥锁,等待条件变量的通知来唤醒线程。当线程被唤醒时,会重新获得互斥锁,在重新检查条件是否满足。

在使用条件变量时,需要借助std::condition_variable类和std::unique_lock类。std::condition_variable类负责等待和通知,std::unique_lock类则负责加锁和解锁。

#include <mutex>
#include <condition_variable>
 
std::mutex mtx;
std::condition_variable cv;
bool flag = false;
 
void func()
{
    std::unique_lock<std::mutex> lock(mtx);
 
    while (!flag)   // 判断条件是否满足
    {
        cv.wait(lock);  // 等待通知,同时解锁互斥锁,让其他线程访问共享资源
 
        // 如果被唤醒,重新获得互斥锁,重新检查条件是否满足
    }
 
    // 访问共享资源
 
    lock.unlock();  // 解锁
}
 
void notify()
{
    std::unique_lock<std::mutex> lock(mtx);
 
    flag = true;    // 修改条件变量
    cv.notify_all();    // 通知所有等待线程
}

在上面例子中,func()函数执行时需要满足某个条件才能访问共享资源,否则需要等待通知。notify()函数负责修改条件变量并通知所有等待线程。在func()函数中,使用while循环来等待条件变量的改变,同时调用std::condition_variable::wait()函数等待通知。wait()函数会自动解锁互斥锁,并使线程进入等待状态。如果等待时被唤醒,wait()函数会重新获得互斥锁,并重新检查条件是否满足。在notify()函数中,使用cv.notify_all()函数通知所有等待线程,同时条件变量被修改,flag变为true。

  1. 可重入锁

可重入锁(recursive_mutex)是一种能够被同一线程多次加锁的互斥锁,可以在同一线程中多次加锁而不会导致死锁。对于可重入锁,同一线程可以在持有锁的情况下再次加锁,但是在解锁时需要和加锁次数相同,即每次解锁只会解除一次锁。

#include <mutex>
std::recursive_mutex rmtx;
 
void func()
{
    rmtx.lock();    // 第一次加锁
 
    // 访问共享资源
 
    rmtx.lock();    // 第二次加锁
 
    // 访问共享资源
 
    rmtx.unlock();  // 第一次解锁
 
    // 访问共享资源
 
    rmtx.unlock();  // 第二次解锁
}

在上面例子中,rmtx是一个可重入锁,func()函数在访问共享资源时先后两次加锁。在解锁时,需要和加锁次数相同,即使用两次rmtx.unlock()函数。

  1. 读写锁

读写锁是一种特殊的锁,用于解决读写冲突问题。在多线程程序中,读写操作比较频繁且耗时,如果使用互斥锁来保证共享资源的同步,就会产生性能瓶颈。读写锁允许多个线程同时读取共享资源,但是在写入时需要独占锁。

C++标准库没有提供读写锁,但是可以使用第三方库来实现,比如Boost库和Poco库。

Boost库中提供了boost::shared_mutex类用于读写锁,它继承自boost::noncopyable类,不能被拷贝或赋值。

```c++
#include <boost