home..

智能指针是否线程安全

config

先说结论:shared的智能指针不是线程安全的,只是它的计数器是线程安全的

如果要保证线程安全,必须要用锁。可以加锁在使用智能指针的地方,或者加锁在对象使用的地方

统一的编译命令:g++ -O0 -fno-elide-constructors -o smartptr_mt_test smartptr_mt_test.cpp && ./smartptr_mt_test

先看一个不加锁的情况

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <vector>
std::shared_ptr<int> global_instance = std::make_shared<int>(0);
constexpr int max_loop = 10000;

void thread_fcn()
{
    // thread-safe reference counting 
    for (int i = 0; i < max_loop; i++) {
        std::shared_ptr<int> temp = global_instance;
        *temp = *temp + 1;
    }
  
    std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;
}

int main()
{
    *global_instance = 0;
    std::vector<std::thread> threadList;
    for (int i = 0; i < 10; ++i) {
        threadList.push_back(std::thread(thread_fcn));
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    for (auto & thread : threadList)  {
        thread.join();
    }
    std::cout << __FUNCTION__ << "-> global_instance : " << *global_instance << std::endl;
    return 0;
}

如果它是线程安全,应该最后打印10,000 。很明显,结果不符合预期。

global_instance use count : 3
global_instance use count : 4
global_instance use count : 4
global_instance use count : 4
global_instance use count : 3
global_instance use count : 4
global_instance use count : 4
global_instance use count : 2
global_instance use count : 2
global_instance use count : 1
main-> global_instance : 76020

加锁在使用智能指针的地方。通过加锁以后,结果才符合预期

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <vector>
#include <condition_variable>    // std::condition_variable
std::shared_ptr<int> global_instance = std::make_shared<int>(0);
constexpr int max_loop = 10000;

std::mutex mtx;
bool ready = false; // 全局标志位.
std::condition_variable cv;
void thread_fcn()
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果标志位不为 true, 则等待...
        cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,

    // thread-safe reference counting 
    for (int i = 0; i < max_loop; i++) {
        std::shared_ptr<int> temp = global_instance;
        *temp = *temp + 1;
    }
  
    std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;
}

void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 设置全局标志位为 true.
    cv.notify_all(); // 唤醒所有线程.
}

int main()
{
    *global_instance = 0;
    std::vector<std::thread> threadList;
    for (int i = 0; i < 10; ++i) {
        threadList.push_back(std::thread(thread_fcn));
    }
    go(); // go!

    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    for (auto & thread : threadList)  {
        thread.join();
    }
    std::cout << __FUNCTION__ << "-> global_instance : " << *global_instance << std::endl;
    return 0;
}

结果符合预期

global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
main-> global_instance : 100000

或者加锁在对象使用的地方:

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <vector>
class mutex_test {
    mutable std::mutex mtx;
public:
    int x;
    mutex_test(int c) : x(c) {}
    mutex_test(const mutex_test& c) : x(c.x) {}
    mutex_test& operator=(const mutex_test& c) {
        if (&c != this) {
            std::lock(mtx, c.mtx);
            std::lock_guard<std::mutex> lock1(mtx,std::adopt_lock);
            std::lock_guard<std::mutex> lock2(c.mtx,std::adopt_lock);
            x = c.x;
        }
        return *this;
    }
    mutex_test& operator+(const int& c) {
        std::lock_guard<std::mutex> lock(mtx);
        x += c; return *this;
    }
    mutex_test& operator+=(const int& c) {
        std::lock_guard<std::mutex> lock(mtx);
        x += c; return *this;
    }
    friend std::ostream& operator<<(std::ostream& out, const mutex_test& c) {
        std::lock_guard<std::mutex> lock(c.mtx);
        out << c.x;
        return out;
    }
};

std::shared_ptr<mutex_test> global_instance = std::make_shared<mutex_test>(0);
constexpr int max_loop = 10000;

void thread_fcn()
{
    // thread-safe reference counting 
    for (int i = 0; i < max_loop; i++) {
        std::shared_ptr<mutex_test> temp = global_instance;
        *temp = *temp + 1;
    }
    std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;
}

int main()
{
    *global_instance = 0;
    std::vector<std::thread> threadList;
    for (int i = 0; i < 10; ++i) {
        threadList.push_back(std::thread(thread_fcn));
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    for (auto & thread : threadList)  {
        thread.join();
    }
    std::cout << __FUNCTION__ << "-> global_instance : " << *global_instance << std::endl;
    return 0;
}

结果符合预期

global_instance use count : 9
global_instance use count : 9
global_instance use count : 8
global_instance use count : 7
global_instance use count : 6
global_instance use count : 5
global_instance use count : 3
global_instance use count : 3
global_instance use count : 2
global_instance use count : 1
main-> global_instance : 100000

The source code for this article can be found here. If you find any errors or have any comments, please click on the link to raise an issue in the corresponding GitHub repo.

© 2024 wanmyj   •  Powered by Soopr   •  Theme  Moonwalk