Using Multiple images (and threads)

Benjamin Gilbert bgilbert at cs.cmu.edu
Thu Jul 17 22:46:11 EDT 2014


On 07/17/2014 05:12 AM, Derek Magee wrote:
> The solution I’ve been using is to close the images, however
> openslide_close as documented is not thread safe, so I wrap everything
> up with mutexes:
>
> if(!check_openslide()) return false ;
>
>      m_doing_openslide_operation=true ;
>
> // Some thread safe openslide operation
>
>      m_doing_openslide_operation=false ;

As John said, you have a huge mess here.  A thread can clear 
m_doing_openslide_operation while another thread is still executing in 
OpenSlide, and you don't wait for m_doing_openslide_operation to be 
cleared before starting a close().  More generally, you don't atomically 
test-and-set on your lock paths, and you don't have the memory barriers 
necessary to propagate updated values to other CPUs.  Using Sleep() 
rather than notifying waiters can't help your performance, either.  You 
should really use the standard locking primitives for this kind of 
thing.  (The thing you're trying to implement is called a reader-writer 
lock.)

> ERROR:src/openslide-decode-tiff.c:551:_openslide_tiffcache_destroy:
> assertion failed: (tc->outstanding == 0)

This means you're calling openslide_close() on a handle that's still 
being used in another thread.

The proper approach will depend on your use case.

- The reason closing and reopening the slide handle reduces memory use 
is that it frees the handle's tile cache.  If you are continually 
reading from each openslide_t (e.g. for batch processing of the entire 
slide), this can be counterproductive, because the tile cache is there 
to save you CPU time.  In this case, it'd be better to reduce your 
thread count.

- If you're only reading an occasional tile from each slide (e.g. your 
program is a tile server), it'd be better to keep a cache of 
recently-used openslide_t handles, rather than maintaining an 
openslide_t for each slide.  For example:

===
// Not tested or even compiled.  This started out as a "simple example"
// and then turned into a lot of code.  Sigh.

#define MAX_UNUSED_HANDLES 16

struct cache {
   GMutex *lock;
   GHashTable *entries;  // path -> cache_value *
   GQueue *freeable;
}

struct cache_value {
   openslide_t *osr;
   char *path;
   int users;
}

struct cache *cache_init(void) {
   struct cache *cache = g_slice_new0(struct cache);
   cache->lock = g_mutex_new();
   cache->entries = g_hash_table_new(g_str_hash, g_str_equal);
   cache->freeable = g_queue_new();
   return cache;
}

// cache lock must be held
static void _cache_free_one_entry(struct cache *cache) {
   struct cache_value *value = g_queue_pop_head(cache->freeable)
   g_assert(value && value->users == 0);
   g_hash_table_remove(cache->entries, value->path);
   g_free(value->path);
   openslide_close(value->osr);
   g_slice_free(struct cache_value, value);
}

void cache_destroy(struct cache *cache) {
   g_mutex_lock(cache->lock);
   struct cache_value *value;
   while (!g_queue_is_empty(cache->freeable)) {
     _cache_free_one_entry(cache);
   }
   // no cache entries should still be in use
   g_assert(g_hash_table_size(cache->entries) == 0);
   g_mutex_unlock(cache->lock);
   g_hash_table_destroy(cache->entries);
   g_queue_free(cache->freeable);
   g_mutex_free(cache->lock);
   g_slice_free(struct cache, cache);
}

openslide_t *cache_get(struct cache *cache, const char *path) {
   g_mutex_lock(cache->lock);
   struct cache_value *value = g_hash_table_lookup(cache->entries, path);
   if (!value) {
     value = g_slice_new0(struct cache_value);
     value->path = g_strdup(path);
     value->osr = openslide_open(path);
     if (!value->osr || openslide_get_error(value->osr)) {
        abort();  // don't actually do this
     }
     g_hash_table_insert(cache->entries, value->path, value);
   } else if (value->users == 0) {
     // We use the O(n) solution for clarity.  O(1) is possible by
     // tracking the list link in struct cache_value and using
     // g_queue_delete_link().
     g_queue_remove(cache->freeable, value);
   }
   value->users++;
   openslide_t *osr = value->osr;
   g_mutex_unlock(cache->lock);
   return osr;
}

void cache_put(struct cache *cache, const char *path, openslide_t *osr) {
   g_mutex_lock(cache->lock);
   struct cache_value *value = g_hash_table_lookup(cache->entries, path);
   g_assert(value && value->users && value->osr == osr);
   if (--value->users == 0) {
     g_queue_push_tail(cache->freeable, value);
     if (g_queue_get_length(cache->freeable) > MAX_UNUSED_HANDLES) {
       _cache_free_one_entry(cache);
     }
   }
   g_mutex_unlock(cache->lock);
}

void do_some_operation(struct cache *cache, const char *path) {
   openslide_t *osr = cache_get(cache, path);
   openslide_read_region(osr, ...);
   cache_put(cache, path, osr);
}
===

--Benjamin Gilbert



More information about the openslide-users mailing list