Coder Guy
Coder Guy

Reputation: 1933

How to do a "try_lock" with an "upgrade_to_unique_lock"

I want to perform an upgrade_to_unique_lock on an upgrade_lock without blocking but a cursory look at the Boost source code (v1.46.1) shows that this is impossible? Is my approach wrong?

I want to first obtain a read lock on a resource that can also potentially be held for reading by other threads. Then I want to see if it is available for writing and if not (other shared read locks are currently held on it) simply do it later.

// No way to specify the boost::try_to_lock object
explicit upgrade_to_unique_lock(upgrade_lock<Mutex>& m_):
    source(&m_),exclusive(move(*source))

I'm trying to procedurally generate terrain tiles (pages) seamlessly using the midpoint displacement algorithm asynchronously. This requires seeding the subject terrain tile (center) with edges obtained from the 4 surrounding tiles (north, west, east, south). Edges are collected only from neighbors that exist and are fully generated already. The implementation should block on one neighbor tile at a time and not hold read locks of other neighbors freeing them-up to be generated at any time. When a tile is being generated, it must guarantee that no other threads have write locks to its neighbors, read locks are ok. Also, there should not be a daisy-chain of threads waiting on write-locks needlessly (i.e. tiles two tiles apart in distance should be able to generate independently of each other).

My implementation depends on a third-party algorithm that requests pages asynchronously as needed based on viewing distance from a camera. It looks like it can arbitrarily make up to 16 asynchronous requests for pages / tiles in no particular order.

Upvotes: 0

Views: 613

Answers (1)

Howard Hinnant
Howard Hinnant

Reputation: 218750

Straw man answer. I'm pretty sure this isn't a valid answer. But I can't put it in a comment because of formatting concerns.

To generate the center tile I would be tempted to do something like this:

tile::generate_center()
{
    tile& north = ...;
    tile& east = ...;
    tile& south = ...;
    tile& west = ...;
    std::unique_lock<mutex_type> l0(mutex(), std::defer_lock);
    shared_lock<mutex_type> ln(north.mutex(), std::defer_lock);
    shared_lock<mutex_type> le(east.mutex(), std::defer_lock);
    shared_lock<mutex_type> ls(south.mutex(), std::defer_lock);
    shared_lock<mutex_type> lw(west.mutex(), std::defer_lock);
    std::lock(l0, ln, le, ls, lw);
    // This is exclusively locked, neighbors are share locked
    // ...
}

This is using C++11 bits like std::unique_lock and std::defer_lock. I believe you can get this functionality from boost too (not positive though).

This routine atomically exclusively locks the center, and share-locks the neighbors. It blocks until it can get all of the locks. It doesn't prevent its neighbors from doing the same thing. That is, use of std::lock will ensure there is no deadlock.

I'm very much unsure that this is actually addressing what you're trying to do. But perhaps it will help lead to an answer...

Upvotes: 2

Related Questions