ObjectStore C++ Advanced API User Guide
Chapter 3
Threads
ObjectStore supports the use of multiple threads within a client application. The key to developing a successful multithreaded application with ObjectStore is in choosing the right combination of transaction, process, and thread models. This is evident once you have good understanding of the process requirements and ObjectStore's implementation of transactions and how it accomplishes thread safety.
ObjectStore Thread Safety
ObjectStore Release 5.1 provides a thread-safe version of the ObjectStore API. It does this by protecting the body of each API call with a mutex lock that only one thread can acquire at a time.
While ObjectStore supports multithreaded clients, it currently supports only one independent transaction per process. However, multithreaded applications can choose any of the following models for interacting with ObjectStore:
Single-Thread Access
ObjectStore implements thread safety using a global mutex and a technique known as mapaside.
Use of Global Mutex
Most access to ObjectStore API is currently serialized with one global mutex. The only exception to this is within the collection subsystem, which selectively protects a subset of the collection API. The one mutex lock protects all libos entry points and some of the collection entry points (such as queries). It does not protect all the collection entry points. This allows multiple threads to manipulate separate collections without blocking one another.
Implications when using ObjectStore collections
This means that you must take care to ensure that these operations do not interfere with one another when two or more threads manipulate a single collection. You might choose to prevent the interference by using an application mutex. (Therefore you are not necessarily thread safe if two threads are operating on the same collection that has already been mapped into memory.)
ObjectStore Release 5.1 is organized to use independent mutexes for different subsystems, but currently implements one mutex. This mutex serializes both implicit access (using a page fault) and explicit access (using an API call).
Mapaside Technique
ObjectStore's unique memory mapping architecture creates a condition that cannot be protected by a simple mutex. In order to handle such a condition ObjectStore uses the technique called mapaside.
Example: mapaside
Consider an example in which two independent threads dereference a pointer to a nonmapped page X. If Thread 1 dereferences the pointer first, it causes a page fault on page X. This page fault acquires the global mutex, then fetches, maps, and relocates page X into the client's address space. During relocation this page must be writable so ObjectStore can relocate the page. A problem occurs if Thread 2 dereferences a pointer to page X during this relocation step. Since the page is writable ObjectStore will never be notified (no page fault occurs); thus, the global mutex is not checked.
How mapaside works
Since Thread 2 could access page X while page X is in an inconsistent state, ObjectStore must
Platform-specific considerations
The cost of mapaside can vary widely across platforms. It depends upon the cost of the extra mmap calls (two additional mmaps) or - in a non-file-based case (for example, HP-UX device driver) - the extra memmove.
Transactions
Independent of thread safety, ObjectStore Release 5.1 has a restriction that a process can have only one top-level transaction opened per process. This means that you must choose one of the following models:
ObjectStore provides locally scoped transactions for two reasons:
- Single thread per transaction supports lexical transactions that can restart in case of deadlock. This means that only one thread can be accessing persistent data at a time. Since this is the case, you would not encounter a situation similar to the mapaside example; therefore, mapaside is unnecessary and can be turned off for locally scoped transactions.
- Lexical transactions implement restart by unwinding their execution stacks and retrying the transaction. Threads do not share execution stacks, so lexical transactions need to be locally scoped.
Dynamic transactions can be scoped either locally or globally. Locally scoped transactions are serialized within a process. Globally scoped transactions must be managed by the application. The application needs to guarantee that no thread is accessing the database during a commit or abort.
Optimizing Transactions in Threaded Environments
In a threaded environment, optimize ObjectStore by using these techniques when appropriate for your application:
Multiple-Threaded Application Models
Two major models for multithreaded applications are
The first model is ideally suited for ObjectStore global transactions. The second model is very typical in application and data servers. For example, the second model is particularly suitable for Internet and intranet applications. These applications are currently very server-centric, where the network browser (front-end GUI) sends commands to the server application for processing by means of the common graphical interface (CGI).
Logically, each simple client application, also known as a thin client, interacts with an application server in independent work units. When planning for read-only operations, sharing transactions works well, because there is no risk of interference between such operations. However, when considering update, or write, operations, sharing transactions does not work because of the potential for
The key is to isolate the actions of writers from read-only operations. Methods of achieving the independence can rely on varied server application architectures. Several alternatives are described in the paragraphs that follow.
One Multithreaded Process
Simple single process architecture
The simplest process architecture would be a single process that supports all clients. Within this process you want the ability to handle multiple concurrent read-only units of work within a global transaction. You can provide a transaction manager that is responsible for serializing write transactions (which can be local transactions) and coordinating the read transaction boundaries in relationship to the write transactions.
The diagram that follows depicts such an arrangement. The thin client refers to a simple client application operation.
Advantages to this architecture
This architecture has several advantages:
Disadvantages to this architecture
The disadvantages of this design are that
Complex single process architecture
A more complicated variation for a single process would allow multiple client write transactions (units of work) per single global (or local) transaction. With this architecture you allow multiple units of work to be carried out regardless of reader or writer and return success before transaction commit. This design is more difficult since you must log input events so that they can be replayed later in case of transaction failure.
Advantages to this architecture
The advantages of this architecture are
Disadvantages to this architecture
This architecture has several disadvantages:
Separate Read/Write Multithreaded Processes
Another simple approach to properly handling read and write units of work is to have separate processes for readers and writers. The reader process (or processes) use global transactions and keep the transaction open for multiple units of work. The writer process (or processes) use local transactions and commit after each unit of work.
The writer process
Ideally, a work manager should be made responsible for selecting an appropriate writer process (from a pool of writers) for a unit of work based upon selection criteria.
The selection criteria can be
The reader process
This type of process architecture is very easy to build using ObjectForms. By using a separate service name for update and read units of work, you can easily direct which server the client application communicates with. For the read-only service, you can set up the service to use multiple threads. For the write (update) service you can set up the service to use multiple processes.
Since you have a process dedicated to read-only access you can configure this process to open the database in an MVCC read-only mode and coordinate the transaction boundaries using notification from the writers. This allows you to
Selecting the Right Application Design
In order to build the proper server application with ObjectStore or another database with this challenge, you must balance transaction boundaries, process model, and thread model with application requirements and development complexity.
[previous] [next]
Copyright © 1997 Object Design, Inc. All rights
reserved.
Updated: 03/31/98 15:28:02