Using Multiple images (and threads)
Derek Magee
D.R.Magee at leeds.ac.uk
Fri Jul 18 04:32:20 EDT 2014
Thanks guys, the mutex stuff was written at 6am to try an work out if it
was an openslide bug, or just me being stupid [I hadn't spotted the non
thread safety of openslide-close in the docs until half way through, so
was trying to work it out for myself]. Mea culpa, a bit crap. I'm
actually using a "proper" threads library (part of Poco pocoproject.org
, check it out, I really like it), but hadn't worked out how to do a
read-write mutex using Poco (a quick google later suggests Poco has a
class RWLock so I'll give that a go. I'll let you know if I have an
issues (putting a "proper" mutex round everything seemed to cure the
crash - hard to tell for sure as it was a rare event - but made
everything REALLY slow, so it probably is my dodgy mutex code at fault).
I'd suspected a tile cache, or something similar!
Thanks again
D.
On 18/07/2014 03:46, Benjamin Gilbert wrote:
> 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
>
> _______________________________________________
> openslide-users mailing list
> openslide-users at lists.andrew.cmu.edu
> https://lists.andrew.cmu.edu/mailman/listinfo/openslide-users
More information about the openslide-users
mailing list