为什么std :: weak_ptr :: expired已优化? [英] Why is std::weak_ptr::expired optimized away?

查看:286
本文介绍了为什么std :: weak_ptr :: expired已优化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的代码中, while(!Ref.expired()); 被快乐地优化成一个无限循环。如果代码行更改为 while(!Ref.lock()); 。一切正常工作。所以两个问题真的:



1)如果 std :: weak_ptr :: expired()访问一个内存保护计数器?



2) Ref.lock()

 


< include< iostream>
#include< memory>
#include< thread>
#include< chrono>

class A
{
public:

A()
{
m_SomePtr = std :: make_shared< bool> (false);
}

virtual〜A()
{
std :: weak_ptr< bool> Ref = m_SomePtr;
m_SomePtr.reset();

// Spin(将在发布版本中优化为无限循环)
while(!Ref.expired());
}

std :: shared_ptr< bool> GetPtr()const {return m_SomePtr; }

private:
std :: shared_ptr< bool> m_SomePtr;
};

class B
{
public:
B(std :: shared_ptr< bool> SomePtr):m_Ref(SomePtr){}

void LockPtr(){m_SomePtr = m_Ref.lock(); }
void UnLockPtr(){m_SomePtr.reset(); }

private:
std :: shared_ptr< bool> m_SomePtr;
std :: weak_ptr< bool> m_Ref;
};

int main()
{
std :: unique_ptr< A> a(new A());
std :: unique_ptr< B> b(new B(a-> GetPtr()));

b-> LockPtr();

std :: cout<< 开始<< std :: endl;

std :: thread first([&]()
{
std :: this_thread :: sleep_for(std :: chrono :: seconds(5));
b-> UnLockPtr();
});

std :: thread second([&]()
{
a.reset(nullptr);
}

first.join();
second.join();

std :: cout<< 完成<< std :: endl;
return 0;
}


解决方案

共享所有权指针设置不用于同步。



[intro.multithread] / 24:


实现可以假设任何线程最终都会执行以下操作之一:

- terminate,

- 调用库I / O函数,

- 访问或修改volatile对象,或者
- 执行同步操作或原子操作。 p>

std :: weak_ptr :: expired()不是同步操作或原子操作;所有的标准说,它不介绍数据竞赛。由于图书馆缺陷2316 < a>, std :: weak_ptr :: lock()被认为是原子操作,所以回答2)你的代码使用 Ref.lock 自C ++ 14起生效。



现在,如果您尝试创建自己的库实现 weak_ptr 使用语言和库设施,它必须使用同步和/或原子操作设施,因此用户提供的 weak_ptr :: expired() / code>可以确定旋转(执行将不得不确保线程最终取得进展,每 [intro.multithread] / 2和/ 25)。但是一个实现没有义务限制自己的库到语言和库设施。



我不完全确定编译器如何优化访问 expired()。我猜想MSVC库是利用x86内存模型的方面,编译器/优化器观察不到C ++内存模型的保证。


In the following code, while ( !Ref.expired() ); is joyfully optimized into an infinite loop. If the line of code is changed to while ( !Ref.lock() );. everything works as expected. So two questions really:

1) How can the compiler optimize away expired when std::weak_ptr::expired() accesses a memory-fenced counter?

2) Is Ref.lock() actually safe, or could this too be optimized away?

Sample code below.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>

class A
{
public:

    A() 
    { 
        m_SomePtr = std::make_shared<bool>( false );
    }

    virtual ~A()
    {
        std::weak_ptr<bool> Ref = m_SomePtr;
        m_SomePtr.reset();

        // Spin (will be optimised into an infinite loop in release builds)
        while ( !Ref.expired() );
    }

    std::shared_ptr<bool> GetPtr() const { return m_SomePtr; }

private:
    std::shared_ptr<bool> m_SomePtr;
};

class B
{
public:
    B( std::shared_ptr<bool> SomePtr ) : m_Ref( SomePtr ) {}

    void LockPtr() { m_SomePtr = m_Ref.lock(); }
    void UnLockPtr() { m_SomePtr.reset(); }

private:
    std::shared_ptr<bool> m_SomePtr;
    std::weak_ptr<bool> m_Ref;
};

int main()
{
    std::unique_ptr<A> a( new A() );
    std::unique_ptr<B> b( new B( a->GetPtr() ) );

    b->LockPtr();

    std::cout << "Starting " << std::endl;

    std::thread first( [&]()
    {
        std::this_thread::sleep_for( std::chrono::seconds( 5 ) );
        b->UnLockPtr();
    } );

    std::thread second( [&]()
    {
        a.reset( nullptr );
    } );

    first.join();
    second.join();

    std::cout << "Complete" << std::endl;
    return 0;
}

解决方案

Your program is incorrect; the shared-ownership pointer facilities are not intended to be used for synchronization.

[intro.multithread]/24:

The implementation may assume that any thread will eventually do one of the following:
— terminate,
— make a call to a library I/O function,
— access or modify a volatile object, or
— perform a synchronization operation or an atomic operation.

std::weak_ptr::expired() is not a synchronization operation or an atomic operation; all the Standard says is that it does not introduce a data race. Since the resolution to Library defect 2316, std::weak_ptr::lock() is considered an atomic operation, so to answer 2) your code using Ref.lock() is valid as of C++14.

Now, it's true that if you were to attempt to create your own library implementation of weak_ptr using the language and library facilities, it would necessarily use the synchronization and/or atomic operation facilities, so a user-provided weak_ptr::expired() would be OK to spin on (the implementation would be obliged to ensure that the thread eventually made progress, per [intro.multithread]/2 and /25). But an implementation is not obliged to restrict its own library to the language and library facilities.

I'm not entirely sure how the compiler is optimizing away the access to expired(). I'd guess that the MSVC library is exploiting aspects of the x86 memory model that the compiler/optimizer observes are not guaranteed by the C++ memory model.

这篇关于为什么std :: weak_ptr :: expired已优化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆