6

現在、iveは以下を使用していくつかの参照カウントクラスを取得しています。

class RefCounted
{
public:
    void IncRef()
    {
        ++refCnt;
    }
    void DecRef()
    {
        if(!--refCnt)delete this;
    }
protected:
    RefCounted():refCnt(0){}
private:
    unsigned refCnt;
    //not implemented
    RefCounted(RefCounted&);
    RefCounted& operator = (RefCounted&};
};

また、参照カウントを処理するスマートポインタークラスもありますが、すべてが均一に使用されているわけではありません(たとえば、IncRefおよびDecRef呼び出しの数を最小限に抑えたパフォーマンスクリティカルなコードの1ビットまたは2ビットで)。

template<class T>class RefCountedPtr
{
public:
    RefCountedPtr(T *p)
    :p(p)
    {
        if(p)p->IncRef();
    }
    ~RefCountedPtr()
    {
        if(p)p->DecRef();
    }
    RefCountedPtr<T>& operator = (T *newP)
    {
        if(newP)newP->IncRef();
        if(p)   p   ->DecRef();
        p = newP;
        return *this;
    }
    RefCountedPtr<T>& operator = (RefCountedPtr<T> &newP)
    {
        if(newP.p)newP.p->IncRef();
        if(p)     p     ->DecRef();
        p = newP.p;
        return *this;
    }
    T& operator *()
    {
        return *p;
    }
    T* operator ->()
    {
        return p;
    }
    //comparison operators etc and some const versions of the above...
private:
    T *p;
};

クラス自体の一般的な使用については、リーダー/ライターロックシステムを使用する予定ですが、IncRefおよびDecRefの呼び出しごとにライターロックを取得する必要はありません。

また、IncRef呼び出しの直前にポインターが無効になる可能性があるシナリオについても考えました。次のことを考慮してください。

class Texture : public RefCounted
{
public:
    //...various operations...
private:
    Texture(const std::string &file)
    {
        //...load texture from file...
        TexPool.insert(this);
    }
    virtual ~Texture()
    {
        TexPool.erase(this);
    }
    freind CreateTextureFromFile;
};
Texture *CreateTexture(const std::string &file)
{
    TexPoolIterator i = TexPool.find(file);
    if(i != TexPool.end())return *i;
    else return new Texture(file);
}
ThreadA ThreadB
t = CreateTexture( "ball.png");
t-> IncRef();
... use t ... t2 = CreateTexture( "ball.png"); // return * t
...スレッドが中断されました...
t-> DecRef();//削除t..。
... t2-> IncRef();//エラー

したがって、参照カウントモデルを完全に変更する必要があると思います。デザインに戻った後に参照を追加した理由は、次のようなものをサポートするためでした。

MyObj->GetSomething()->GetSomethingElse()->DoSomething();

する必要はなく:

SomeObject a = MyObj->GetSomething();
AnotherObject *b = a->GetSomethingElse();
b->DoSomething();
b->DecRef();
a->DecRef();

マルチスレッド環境でC++で高速参照カウントを行うためのクリーンな方法はありますか?

4

10 に答える 10

16

参照カウントをアトミックにすると、ロックは必要ありません。Windowsでは::InterlockedIncrementと::InterlockedDecrementを使用できます。C ++ 0xでは、atomic<>があります。

于 2009-07-16T14:32:17.077 に答える
10

それが特定のボトルネックであることがわかっていない限り、私はただ使用しますboost::shared_ptr

これは非常に高速ですが、余分な制御ブロックが割り当てられるため、余分なオーバーヘッドが少し発生します。一方で、多くの利点があります。

  • ポータブルです
  • 正しいです
  • 精神的なサイクルを無駄にする必要はありません。実際に物事を成し遂げるための時間を残します。
  • 速いです
  • これは業界標準であり、他のプログラマーはすぐに理解できます。
  • boostそうでない場合は、する必要があるものを使用する必要があります

また、参照カウントされたオブジェクトのリーダー/ライター ロックはおそらく必要ないことに注意してください。競合は最小限に抑えられ、追加のオーバーヘッドは、得られる利点を完全に圧倒します。共有ポインタは、チップ レベルのアトミック int 操作で実装されます。これは、通常のミューテックスよりもはるかに優れており、リーダー/ライター ロックよりも大幅に高速です。

于 2009-07-16T14:39:50.527 に答える
2

osg、OpenSceneGraph はこのような構造になっています。

osg::Referenced からクラスを派生させ、マルチスレッドであってもデストラクタを気にしません。

次のようにクラスを作成するだけです:

osg::ref_ptr<MyClass> m = new MyClass();

それ以外の:

MyClass* m = new MyClass();
于 2009-07-16T14:36:24.433 に答える
2

スレッドセーフまたはアトミックスレッドセーフが必要ですか? boot::shared_ptr は単にスレッドセーフです。安全にコピーするには、shared_ptr を「所有」する必要があります。

http://atomic-ptr-plus.sourceforge.net/で、原子的にスレッドセーフな参照カウントで私が行ったいくつかの実験的なものがあり ます。これにより、何が関係しているかがわかります。

于 2009-07-17T02:51:20.710 に答える
1

boost::shared_ptr と Poco::SharedPtr はどちらも、このイディオムを独立したスマート ポインターでラップします。

上記で示したように、侵入的な参照カウントが必要な場合は、Poco の AutoPtr が適切に機能する実装です。

編集: リンクを追加したかったのですが、評判が低すぎました。クラス名のいずれかを Google で検索すると、道がわかるはずです。

于 2009-07-16T14:42:27.727 に答える
1

キャッシュは aboost::weak_ptrまたは同様の構造を使用する必要があります。

于 2009-07-16T21:35:41.280 に答える
1

主な問題は、CreateTexture が戻る前に参照を取得しないことです。このようにオープンコーディングしている場合、それを処理する最も簡単な方法は、次のように、削除前に参照を解放するときにも取得される TexPool の周りにロックを設定することです。

// PSEUDOCODE WARNING: --refcnt MUST be replaced by an atomic decrement-and-test
// Likewise, AddRef() MUST use an atomic increment.
void DecRef() {
    if (!--refcnt) {
        lock();
        if (!refcnt)
            delete this;
        unlock();
    }
}

と:

Texture *CreateTexture(const std::string &file)
{
    lock();

    TexPoolIterator i = TexPool.find(file);
    if(i != TexPool.end()) {
        *i->AddRef();
        unlock();
        return *i;
    }
    unlock();
    return new Texture(file);
}

つまり、他の人が言及したように、boost::shared_ptr (別名 std::tr1::shared_ptr) はこれをすべてロックレスで安全な方法で実装し、テクスチャ キャッシュに役立つウィーク ポインターもサポートしています。

于 2009-07-16T15:01:08.440 に答える
0

この pdf を見てみましょう: http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf

これは、ロックを必要としない参照カウントシステムについて説明しています。(ロックとしてカウントできるスレッドを一度に 1 つずつ「一時停止」する必要があります。) また、ガベージ サイクルも収集します。欠点は、それがはるかに複雑であることです。また、読者の演習としていくつかの重要なことが残されています。新しいスレッドが作成されたときや古いスレッドが削除されたときに何が起こるか、または本質的に非周期的なオブジェクトを処理する方法など。(これを行うことにした場合は、これらをどのように使用したかをお知らせください。)

于 2009-07-20T23:50:01.247 に答える
0

この特定の設計にはクリティカル セクションが必要だと思います。これが必要な場所の 1 つは CreateTexture です。そうしないと、システム内に複数の同一のテクスチャ オブジェクトが存在するリスクに遭遇するからです。また、一般に、複数のスレッドが同じテクスチャを作成および破棄できる場合、それは「変更可能な共有状態」になります。

于 2009-07-16T22:05:56.817 に答える