Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Locking a shared_ptr

I have an shared object that need to be send to a system API and extract it back later. The system API receives void * only. I cannot use shared_ptr::get() because it do not increases the reference count and it could be released by other threads before extract from the system API. Sending a newed shared_ptr * will work but involves additional heap allocation.

One way to do it is to let the object derived from enable_shared_from_this. However, because this class template owns only a weak_ptr, it is not enough to keep the object from released.

So my solution looks like the following:

class MyClass:public enable_shared_from_this<MyClass> {
private:
    shared_ptr<MyClass> m_this;
public:
    void *lock(){
        m_this=shared_from_this();
        return this;
    }
    static shared_ptr<MyClass> unlock(void *p){
        auto pthis = static_cast<MyClass *>(p);
        return move(pthis->m_this);
    }
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());

Are there any easier way to do this?

The downside of this solution:

  • it requires an additional shared_ptr<MyClass> in the MyClass object layout, in addition of a weak_ptr in the base class enable_shared_from_this.

  • As I mentioned in the comments, access to lock() and unlock() concurrently is NOT Safe.

  • The worst thing is that this solution can only support lock() once before a call of unlock(). If the same object is to be use for multiple system API calls, additional reference counting must be implemented.

If we have another enable_lockable_shared_from_this class it will be greate:

class MyClass:public enable_lockable_shared_from_this<MyClass> {
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj.lock());
/* ... */
auto punlocked = unlock_shared<MyClass>(system_api_reveive_obj());

And the implementation of enable_lockable_shared_from_this is similar as enable_shared_from_this, the only difference is it implements lock() and a helper function unlock_shared. The calling of those functions only explicitly increase and decrease use_count(). This will be the ideal solution because:

  • It eliminate the additional space cost

  • It reuses the facilities existing for shared_ptr to guarantee concurrency safety.

  • The best thing of this solution is that it supports multiple lock() calls seamlessly.

However, the only biggest downside is: it is not available at the moment!

UPDATE:

At least two answers to this question involves a container of pointers. Please compare those solutions with the following:

class MyClass:public enable_shared_from_this<MyClass> {
private:
    shared_ptr<MyClass> m_this;
    mutex this_lock; //not necessory for single threading environment
    int lock_count;
public:
    void *lock(){
        lock_guard lck(this_lock); //not necessory for single threading environment
        if(!lock_count==0)
            m_this=shared_from_this();
        return this;
    }
    static shared_ptr<MyClass> unlock(void *p){
        lock_guard lck(this_lock); //not necessory for single threading environment
        auto pthis = static_cast<MyClass *>(p);
        if(--lock_count>0)
            return pthis->m_this;
        else {
            lock_count=0;
            return move(pthis->m_this); //returns nullptr if not previously locked
        }
    }
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());

This is absolutely an O(1) vs O(n) (space; time is O(log(n)) or similar, but absolutely greater than O(1) ) game.

like image 690
Earth Engine Avatar asked Nov 09 '11 23:11

Earth Engine


2 Answers

I am now have an idea of the following:

template<typename T>
struct locker_helper{
    typedef shared_ptr<T> p_t;
    typedef typename aligned_storage<sizeof(p_t), alignment_of<p_t>::value>::type s_t;
};
template<typename T> void lock_shared(const shared_ptr<T> &p){
    typename locker_helper<T>::s_t value;
    new (&value)shared_ptr<T>(p);
}
template<typename T> void unlock_shared(const shared_ptr<T> &p){
    typename locker_helper<T>::s_t value
        = *reinterpret_cast<const typename locker_helper<T>::s_t *const>(&p);
    reinterpret_cast<shared_ptr<T> *>(&value)->~shared_ptr<T>();
}


template<typename T>
void print_use_count(string s, const shared_ptr<T> &p){
    cout<<s<<p.use_count()<<endl;
}

int main(int argc, char **argv){
    auto pi = make_shared<int>(10);
    auto s = "pi use_count()=";
    print_use_count(s, pi); //pi use_count()=1
    lock_shared(pi);
    print_use_count(s, pi);//pi use_count()=2
    unlock_shared(pi);
    print_use_count(s, pi);//pi use_count()=1
}

and then we can implement the original example as following:

class MyClass:public enable_shared_from_this { /*...*/ };

/* ... */
auto pobj = make_shared<MyClass>(...);
/* ... */
lock_shared(pobj);
system_api_send_obj(pobj.get());
/* ... */
auto preceived = 
    static_cast<MyClass *>(system_api_reveive_obj())->shared_from_this();
unlock_shared(preceived);

It is easy to implement a enable_lockable_shared_from_this with this idea. However, the above is more generic, allows control things that is not derived from enable_lockable_from_this` template class.

like image 177
Earth Engine Avatar answered Oct 04 '22 18:10

Earth Engine


Why not just wrap the void * API in something that tracks the lifetime of that API's references to your object?

eg.

typedef std::shared_ptr<MyClass> MyPtr;
class APIWrapper
{
    // could have multiple references to the same thing?
    // use multiset instead!
    // are references local to transient messages processed in FIFO order?
    // use a queue!  ... etc. etc.
    std::set<MyPtr, SuitableComparator> references_;

public:
    void send(MyPtr const &ref)
    {
        references_.insert(ref);
        system_api_send_obj(ref.get());
    }
    MyPtr receive(void *api)
    {
        MyPtr ref( static_cast<MyClass *>(api)->shared_from_this() );
        references_.erase(ref);
        return ref;
    }
};

Obviously (hopefully) you know the actual ownership semantics of your API, so the above is just a broad guess.

like image 20
Useless Avatar answered Oct 04 '22 19:10

Useless