5.4 Multi-threading

MuPDF itself does not rely on a thread system, but it will make use of one if one is present. This is crucial to ensure that MuPDF can be called from multiple threads at once.

A typical example of this might be in a multi-core processor on a printer. We can interpret the PDF file to a display list, and then render ‘bands’ from that display list to send to the printer. By using multiple threads we can render multiple bands at once, thus vastly improving processing times.

In this example, although each thread will be rendering different things, they will probably share some information - for instance the same font is likely to be used in multiple bands. Rather than have every thread render all the glyphs that it needs from the font independently, it would be nice if they could collaborate and share results.

We therefore arrange that data structures such as the font cache can be shared between the different threads. This, however, brings dangers; what if two threads try to write to the same data structure at once?

To save this being a problem, we rely on the user providing some locking functions for us.

/* 
   Locking functions 
 
   MuPDF is kept deliberately free of any knowledge of particular 
   threading systems. As such, in order for safe multi-threaded 
   operation, we rely on callbacks to client provided functions. 
 
   A client is expected to provide FZ_LOCK_MAX number of mutexes, 
   and a function to lock/unlock each of them. These may be 
   recursive mutexes, but do not have to be. 
 
   If a client does not intend to use multiple threads, then it 
   may pass NULL instead of a lock structure. 
 
   In order to avoid deadlocks, we have one simple rule 
   internally as to how we use locks: We can never take lock n 
   when we already hold any lock i, where 0 <= i <= n. In order 
   to verify this, we have some debugging code, that can be 
   enabled by defining FITZ_DEBUG_LOCKING. 
*/ 
 
typedef struct 
{ 
   void *user; 
   void (*lock)(void *user, int lock); 
   void (*unlock)(void *user, int lock); 
} fz_locks_context; 
 
enum { 
   ... 
   FZ_LOCK_MAX 
};

If MuPDF is to be used in a multi-threaded environment, then the user is expected to define FZ_LOCK_MAX locks (currently 4, though this may change in future), together with functions to lock and unlock them.

In pthreads, a lock might be implemented by pthread_mutex_t. In windows, either Mutex or a CriticalSection might be used (the latter being more lightweight).

These locks are not assumed to be recursive (though recursive locks will work just fine).

To avoid deadlocks, MuPDF guarantees never to take lock n if that thread already holds lock m (for n > m).

There are 3 simple rules to follow when using MuPDF in a multi-threaded environment:

  1. No simultaneous calls to MuPDF in different threads are allowed to use the same context.

    Most of time it is simplest just to use a different context for every thread; just create a new context at the same time as you create the thread. See section 5.5 Cloning for more information.

  2. No simultaneous calls to MuPDF in different threads are allowed to use the same document.

    Only one thread can be accessing an document at a time. Once display lists are created from that document, multiple threads can operate on them safely.

    The document can safely be used from several different threads as long as there are safeguards in place to prevent the usages being simultaneous.

  3. No simultaneous calls to MuPDF in different threads are allowed to use the same device.

    Calling a device simultaneously from different threads will cause it to get confused and may crash. Calling a device from several different threads is perfectly acceptable as long as there are safeguards in place to prevent the calls being simultaneous.