Pertanyaan Mengapa std :: weak_ptr :: kedaluwarsa dioptimalkan?


Dalam kode berikut, while ( !Ref.expired() ); dioptimalkan dengan senang menjadi loop tak terbatas. Jika baris kode diubah menjadi while ( !Ref.lock() );. semuanya berjalan sesuai harapan. Jadi, dua pertanyaan benar-benar:

1) Bagaimana kompilator mengoptimalkan pergi kedaluwarsa ketika std::weak_ptr::expired() mengakses penghitung memori-dipagari?

2) Apakah Ref.lock() sebenarnya aman, atau bisakah ini juga dioptimalkan pergi?

Contoh kode di bawah ini.

#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;
}

32
2018-01-23 14:05


asal


Jawaban:


Program Anda salah; fasilitas pointer kepemilikan bersama tidak dimaksudkan untuk digunakan untuk sinkronisasi.

[intro.multithread]/ 24:

Implementasi dapat mengasumsikan bahwa setiap thread pada akhirnya akan melakukan salah satu dari yang berikut:
  - mengakhiri,
  - membuat panggilan ke fungsi I / O perpustakaan,
  - mengakses atau memodifikasi objek yang mudah menguap, atau
  - melakukan operasi sinkronisasi atau operasi atom.

std::weak_ptr::expired() bukan operasi sinkronisasi atau operasi atom; semua Standar mengatakan bahwa itu tidak memperkenalkan perlombaan data. Sejak resolusi ke Cacat perpustakaan 2316, std::weak_ptr::lock() dianggap sebagai operasi atom, jadi untuk menjawab 2) kode Anda menggunakan Ref.lock() berlaku pada C ++ 14.

Sekarang, memang benar bahwa jika Anda mencoba untuk membuat implementasi perpustakaan Anda sendiri weak_ptr menggunakan fasilitas bahasa dan perpustakaan, itu akan selalu menggunakan sinkronisasi dan / atau fasilitas operasi atom, sehingga disediakan oleh pengguna weak_ptr::expired() akan OK untuk berputar (implementasi akan diwajibkan untuk memastikan bahwa thread akhirnya membuat kemajuan, per [intro.multithread]/ 2 dan / 25). Tetapi sebuah implementasi tidak diwajibkan untuk membatasi perpustakaannya sendiri ke fasilitas bahasa dan perpustakaan.

Saya tidak sepenuhnya yakin bagaimana kompilator mengoptimalkan akses ke expired(). Saya kira pustaka MSVC mengeksploitasi aspek dari model memori x86 yang diamati oleh pengontrol / pengoptimal tidak dijamin oleh model memori C ++.


8
2018-01-23 18:00