IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY by François Bergeon A DISSERTATION Submitted to The University of Liverpool in partial fulfillment of the requirements for the degree of MASTER OF SCIENCE Information Technology (Software Engineering) January 5, 2009
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
IMPLEMENTING CONTAINER CLASSES IN SHARED
MEMORY
by
François Bergeon
A DISSERTATION
Submitted to
The University of Liverpool
in partial fulfillment of the requirements for the degree of
MASTER OF SCIENCE Information Technology (Software Engineering)
January 5, 2009
ABSTRACT
IMPLEMENTING CONTAINER CLASSES IN SHARED
MEMORY
by
François Bergeon
The subject of this dissertation is the implementation of container classes
in shared memory in order to allow concurrent high-level access to easily
retrievable structured data by more than one process.
The project extends the sequence containers and associative containers
offered by the C++ Standard Template Library (STL) in the form of a
“Shared Containers Library” which purpose is to allocate both the
containers and the objects they contain in shared memory to make them
accessible by other processes running on the same machine.
Shared STL-like containers can be used for a variety of implementations
that require high-level sharing of structured and easily retrievable data
between processes. Applications of shared containers may include real-
time monitoring, metrics gathering, dynamic configuration, distributed
processing, process prioritization, parallel cache seeding, etc.
The Shared Container Library presented here consists of a binary library
file, a suite of C++ header files, and the accompanying programmers’
documentation. STL-type containers can be allocated in a shared memory
segment thanks to a class factory mechanism that abstracts the
complexity of the implementation. The Shared Containers Factory offers
synchronization for concurrent access to containers in the form of a
mechanism offering both exclusive and a non-exclusive locks.
The target platform of the Shared Container Library is the Microsoft
implementation of the STL for the 32bit Microsoft Windows operating
systems (Win32) as distributed with the Microsoft Visual Studio 2008
development environment and its Microsoft Visual C++ compiler. Further
developments of this library may include porting to the Unix platform and
designing a similar library for other languages such as Java and C#.
DECLARATION
I hereby certify that this dissertation constitutes my own product, that
where the language of others is set forth, quotation marks so indicate, and
that appropriate credit is given where I have used the language, ideas,
expressions, or writings of another.
I declare that the dissertation describes original work that has not
previously been presented for the award of any other degree of any
institution.
Signed,
François Bergeon
ACKNOWLEDGEMENTS
The author wishes to thank Martha McCormick, the dissertation advisor for the project, for
her support, kind words and constructive comments.
The author thanks the members of the EFSnet development team for their insight and
support during the development of an online transaction processing system when the
idea of implementing containers in shared memory for monitoring and dynamic
configuration was born.
Thanks to Eddy Luten for his high-resolution timer code that is used in the test
scaffolding. Thanks to Gabriel Fleseriu and Andreas Masur for pointing out the adequacy
of segregated storage in custom allocators. Thanks to Ion Gaztañaga for his work on the
Boost.Interprocess library and to Dr. Marc Ronell for his prior work on shared allocators.
A special thanks to Roy Soltoff, formerly of Logical Systems Inc, who authored “LC”
(“elsie”), a simple C compiler for the TRS-80 with which the author learned the C
Chapter 2. Background and review of literature 6 2.1 Related Work 6 2.2 Literature 6
Chapter 3. Theory 8 3.1 Generic Programming and the Standard Template Library 8 3.2 Interprocess communication and shared memory 8 3.3 Memory allocation and segregated storage 9 3.4 Object oriented approaches – design patterns 11
4.2.1 Allocation of container objects in shared memory..............................................14 4.2.2 Allocation of container’s contents in shared memory.........................................14 4.2.3 Coherence of internal data structures between processes ...................................15 4.2.4 Container attachment from a non-creating process .............................................15 4.2.5 Reference safety ...................................................................................................16 4.2.6 Concurrent access safety ......................................................................................16
4.3 Non-functional requirements 16
vii
4.4 Approach 17 4.5 Detailed design 20
4.5.1 Overview ..............................................................................................................20 4.5.2 Low level use cases ..............................................................................................23 4.5.3 High-level classes – shared containers and class factories..................................25 4.5.4 Process flows ........................................................................................................28
5.3.1 Naming conventions.............................................................................................32 5.3.2 Structure of library and header files.....................................................................32 5.3.3 Interface to shared memory – pointer consistency ..............................................33 5.3.4 Object instantiation and attachment – map of shared containers ........................35 5.3.5 Synchronization....................................................................................................37 5.3.6 Reference safety ...................................................................................................38
5.4 Verification and validation 38 5.4.1 Unit testing ...........................................................................................................38 5.4.2 Test scaffolding ....................................................................................................39
Listing 1: The shared_object sub-class of shared_pool
The shared map is then defined as shared map of string and pointers to
shared_object objects which is allocated in the segment of shared memory and uses
the shared_allocator custom allocator for its allocation needs (listing 2).
template<class Key, class Data, class Compare=std::less<Key>> class shared_map : public sync, public std::map<Key, Data, Compare, shared_allocator<std::pair<const Key, Data>>> {};
Local memory results were obtained by instantiating native STL classes. The “Shared
memory w/creation” column corresponds to standalone shared container classes. The
creation operation does not include initialization of the shared memory segment and its
corresponding segregated storage memory pool which was independently measured as
adding less than 100ms for a 64Mb shared pool with a chunk size of 64 bytes. The
“Shared memory w/attach” results were obtained by creating a container in one process
and then measuring attach, populate, verify and destroy operations from another
process. In each case, the tests have been run several times and the average results are
being presented here (see appendix C for complete results).
Creation time of the container objects in the “Shared memory w/creation” case is on par
with that of other cases. Response time for “attach” in the “Shared memory w/attach”
case reflects simple attachment to an existing shared container.
Once the shared memory segment is initialized, creation of a shared container is
performed in a similar time than the time required to create a local container. To this
duration, the time O(log n) required to insert a new container into the shared container
map’s balanced binary tree structure needs to be taken into account (where n is the
number of shared containers present in the container map). Similarly, the time required to
attach to an existing container is O(log n), the time required to retrieve the container by
name from the shared container map (Cormen et al. 2001).
45
One important finding is that, except when considering very simple data types, response
time to populate and verify a container in shared memory does not show significant
degradation compared to locally allocated containers. Performance related to populating
a container and verifying its contents does seem to suffer a 30-40% degradation when
simple data types are used (e.g. a vector of integers) but does not significantly differ for
complex types (e.g. a map of strings) whether the container is located on the process
heap or in shared memory. Of course, possible delays due to high disk activity or
concurrency locking requests are not taken into account in the above measured results.
Destruction of a shared memory allocated container proves to be faster than that of a
local one, a fact that may be explained by the fact that the segregated storage algorithm
used in this implementation does not make any attempt at garbage collection whereas
the default heap allocator may perform cleanup tasks when memory is deallocated. In
addition, the short time observed for detach in the “Shared memory w/attach” case
(<1ms) can be explained by the fact that the shared container is not actually erased as
the creating process still holds a reference to the object. In this case, the reference
counter of the shared container is simply decremented.
6.3 Concurrency issues
The postulate that “multithreaded programs are deterministic by nature […] many serious
bugs will not happen all the time, or even with any regularity” (Cohen & Woodring 1998)
was verified during the testing phase of this project.
Testing was conducted on two machines both running the same version of Windows XP
professional with the latest Microsoft updates. A desktop computer with a dual-core
Pentium 6400 processor running at 2.13 Ghz was used for development and testing, and
a laptop with a Pentium M processor at 1.6 Ghz was used for further testing and text
editing. Although concurrent multi-process testing on the laptop did not show any issue,
initial multi-process testing on the desktop computer with a debug version of the code
terminated with a fatal exception: “DEBUG_ERROR("ITERATOR LIST CORRUPTED!")”
46
The error was traced to the “xutility” header of the Dinkumware implementation of the
STL (based on code written by P.J. Plauger) distributed with Microsoft Visual C++ 2008.
Further research showed that the error was in fact linked to an iterator debugging feature
specific to this implementation of the STL. Because the error only occurred on the
desktop and not on the laptop, this issue may only exist on multi-core processors capable
of true multitasking as opposed to single core variants that only simulate multitasking by
the use of time-sharing techniques.
A solution was found in the form of defining the macros “_HAS_ITERATOR_DEBUGGING”
and “_SECURE_SCL” both with a value of zero to turn these additional features off at
compile time (Microsoft 2008 [2], Microsoft 2008 [3]). Once these macros were applied
and the test code recompiled, no additional faults were noted during subsequent multi
process concurrent testing on the desktop computer.
47
Chapter 7. CONCLUSIONS
7.1 Potential applications
Shared STL-like containers can be used for a variety of implementations that require
high-level access to structured data between processes.
Figure 13: Monitoring / configuring an application server
48
Such needs are frequently encountered in concurrent interactive applications where users
are allowed to exchange data. Other applications include monitoring of online transaction
processing system. Databases and local files can be used in lieu of shared containers for
these applications, but these solutions may add overhead, additional failure points and/or
increased code complexity compared to shared containers.
One practical example of shared container could be for a transaction processing system
where one could envision each worker thread of an application server updating a given
set of metrics residing in a shared map that can be read by another process. This other
process could, for example, expose these metrics via the Simple Network Management
(SNMP) protocol. The same SNMP interface could use another shared map to
dynamically modify the internal parameters representing the configuration used by the
worker threads, thereby providing a path to dynamic configuration (figure 13). One could
also envision a separate process using the metrics harvested by worker threads in the
shared container to dynamically change the configuration of the working system in order
to react to connection errors or system-wide congestion (e.g. automated database
failover, etc).
7.2 Lessons Learned
The subject of implementing STL containers in shared memory has attracted quite a bit of
attention over time, possibly because it may look easily feasible to the casual observer,
but proves to be in fact more challenging than originally thought. It would be an
oversimplification to think that implementing a custom allocator targeting a shared
memory segment would be sufficient to reach the goals of this project. In fact, allocating
the data held by the containers in shared memory is only one of several functional
requirements – and maybe one of the easiest to satisfy. Other requirements include
placing the internal indexing structures of the containers in shared memory and making
them consistently accessible by various processes. In addition, such an implementation
would not be complete without an adequate synchronization and reference counting
mechanism that guarantees safe concurrent access to the containers and their contents
49
but releases resources used by objects that are no longer in use. One could therefore
argue that the challenges presented by a project like this one may remain unrecognized
until one starts performing a detailed analysis of the implementation’s requirements.
Functional evaluation on the shared containers library led to a surprising finding in the
form of satisfactory performance on a computer equipped with a single core processor
but an unexpected behavior on a double-core equipped machine. The lesson here is that
multithreaded or multi-process applications should be tested on architectures where true
concurrency can occur – in this case a dual-core Pentium processor – to guarantee that
all potential synchronizing issues have been addressed. Processors with a high degree of
compatibility can still have significant architectural differences that may cause issues in
software programs to remain hidden for a long time.
7.3 Future Activity
The code implemented for this project lacks strong exception handling and this should be
added before the library is used in real-life applications. The simple segregated storage
algorithm does not currently offer a garbage collection mechanism and one could argue
that its usefulness would consequently be limited if numerous allocation/deallocation of
large memory buffers take place. A garbage collection mechanism would be a welcome
addition to the library.
In addition, the current library implementation only performs very limited runtime type
checking when attaching to an existing shared container. Only the memory footprint of
the container object is taken into account (its size returned by sizeof) to verify that the
container expected from a call to ContainerFactory::attach is of the correct type.
One could envision using C++’s runtime type information (RTTI) mechanism using the
typeid operator to verify the container’s type upon attachment by a non-creating
process.
50
Finally, no real-life testing of the shared container library has been performed at this time
and it would stand to reason to think that early practical usage of the library may unveil
issues that were not uncovered during initial testing.
7.4 Prospects for Further Work
The current implementation of the shared container library is limited to the Win32
operating system and the STL library distributed with Microsoft Visual Studio 2008.
Porting the shared containers library to other operating systems such as Unix or Linux
should prove feasible by developing platform-specific versions of the shared heap and the
safe counter classes. Developing a similar library for other languages such as Java and
C# would also be an interesting endeavor, albeit one that would pose its own set of
challenges and may lead to a different approach. For example, if parameterized classes
can be declared in newer versions of Java and C# by using “generics” similar in concept
and syntax to C++’s templates (Bracha 2004 & Microsoft 2008 [4]), neither languages
supports multiple inheritance which is extensively used in this implementation.
Another direction for further work could be to widen the features of the Shared Containers
Library. Additional desirable functionalities can be envisioned such as the possibility to
use more than one memory mapped file – a current limitation of this implementation – the
possibility to persist the memory mapped file on disk once all processes have terminated
execution and the addition of security and data encryption for the shared memory
segments and the containers they hold.
51
REFRENCES CITED
Alexander C. et al. (1977) - "A Pattern Language" - Oxford University Press - ISBN 978-0195019193 – p.x anonymous (n.d.) - "STLshm" - SourceForge.net - [Online] Available at: http://stlshm.sourceforge.net/Introduction.html (Accessed November 25, 2008) Austern, M. (1999) - "Generic Programming and the STL" - Addison Wesley - ISBN 0-201-30956-4 Bartlett, J. (November 16, 2004) - "Inside memory management - The choices, tradeoffs, and implementations of dynamic allocation" - IBM developerWorks - [Online] Available at: http://www.ibm.com/developerworks/linux/library/l-memory/ (Accessed December 14, 2008) Baum, L. & Becker, M. (2000) - "Generic components to foster reuse" - Proceedings of the 37th International Conference on Technology of Object-Oriented Languages and Systems - ISBN 0-7695-0918-5 – pp.266-277 Bennet S. et al. (2006) - "Object-Oriented Systems Analysis And Design Using UML" p.226 - McGraw Hill - ISBN 13-978-0-07-711000-0 Boehm, B.W. (May 5, 1986) - "A spiral model of software development and enhancement " - IEEE Computer - [Online] Available at: http://ieeexplore.ieee.org.ezproxy.liv.ac.uk/iel1/2/6/00000059.pdf?tp=&arnumber=59&isnumber=6 (Accessed December 18, 2008) Bracha G. (July 5, 2004) - "Generics in the Java Programming Language" - Sun Microsystems - [Online] Available: http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf (Accessed December 18, 2008) Buchanan, J (March 27, 2008) - "An Interview with Bjarne Stroustrup" - Dr. Dobb's Journal - [Online] Available at: http://www.ddj.com/cpp/207000124 (Accessed November 17, 2008) Cohen, A. & Woodring, M. (1998) - "Win32 Multithreaded Programming" - O'Reilly - ISBN 1-56592-296-4 – p.570 Cormen, T. et al. (2001) - "Introduction to Algorithms" - McGraw Hill - ISBN 978-0-262-03293-3 – p.253 Dijkstra E.W. (1974) - "On the role of scientific thought - Selected writings on Computing: A Personal Perspective" - Springer-Verlag New York, Inc. - ISBN ISBN 0-387-90652-5 Downey, A.B. (2008) - "The Little Book of Semaphores" - Green Tea Press - [Online] Available at: http://greenteapress.com/semaphores/downey08semaphores.pdf (Accessed December 17, 2008) Fleseriu G. & Masur, A. (February 10, 2004) - "Allocators (STL)" - CodeGuru - [Online] Available at: http://www.codeguru.com/Cpp/Cpp/cpp_mfc/stl/article.php/c4079/ (Accessed October 13, 2008) Frohlïch, P.H. (2002) - "Inheritance Decomposed" - Department of Information and Computer Science University of California, Irvine - [Online] Available: http://www.cs.jyu.fi/~sakkinen/inhws/papers/Froehlich.pdf (Accessed December 10, 2008)
52
Gamma E. et al. (2002) - "Design Patterns - Elements of Reusable Object-Oriented Software" - Pearson Education - ISBN 81-7808-135-0 Gaztañaga , I. (March 28, 2008) - "Boost.Interprocess" – boost C++ libraries - [Online] Available at: https://svn.zib.de/lenne3d/lib/boost/1.35.0/doc/html/interprocess.html (Accessed November 25, 2008) Gray, J.S. (1997) - "Interprocess Communications in Unix" - Prentice Hall - ISBN 0-13-186891-8 – p.193 Kath, R. (February 9, 1993) - "Managing Memory-Mapped Files in Win32" - Microsoft Developer Network Technology Group - [Online] Available at: http://msdn.microsoft.com/en-us/library/ms810613.aspx (Accessed October 7, 2008) Ketema, G. (April 1, 2003) - "Creating STL Containers in Shared Memory" - Dr.Dobb's - [Online] Available at: http://www.ddj.com/cpp/184401639 (Accessed October 7, 2008) Meyers, S. (2001) - "Effective STL" - Addison Wesley - ISBN 0-201-74962-9 – pp.48-58 Microsoft (November 6, 2008 [1]) - "MapViewOfFileEx Function" - Microsoft Corp. - [Online] Available at: http://msdn.microsoft.com/en-us/library/aa366763(VS.85).aspx (Accessed November 25, 2008) Microsoft (2008 [2]) - "Debug Iterator Support" - Microsoft Corp. - [Online] Available at: http://msdn.microsoft.com/en-us/library/aa985982(VS.80).aspx (Accessed December 10, 2008) Microsoft (2008 [3]) - "Checked Iterators" - Microsoft Corp. - [Online] Available at: http://msdn.microsoft.com/en-us/library/aa985965(VS.80).aspx (Accessed December 10, 2008) Microsoft (2008 [4]) - "Introduction to Generics (C# Programming Guide" - Microsoft Corp. - [Online] Available at: http://msdn.microsoft.com/en-us/library/0x6a29h6(VS.80).aspx (Accessed December 18, 2008) Musser, D.R. & Stepanov A.A. (1988) - "Generic programming" - Proceeds of the First International Conference of ISSAC-88 and AAECC-6 - [Online] Available at: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.51.1780&rep=rep1&type=pdf (Accessed December 8, 2008) Musser, D.R. & Stepanov A.A. (1989) - "The Ada Generic Library: Linear List Processing Packages” - Springer Compass International – ISBN: 0387971335
Petzold, C. (1999) - "Programming Windows" - Microsoft Press - ISBN 1-57231-995-X Purdom, P.W. et al. (August 1, 1970) - "Statistical Investigation of Three Storage Allocation Algorithms" - Computer Science Dept. - The University of Wisconsin Madison - [Online] Available at: http://www.cs.wisc.edu/techreports/1970/TR98.pdf (Accessed December 14, 2008) Ronell, M. (2003) - "A C++ Pooled, Shared Memory Allocator For The Standard Template Library" - Proceedings of the Fifth Real Time Linux Workshop - [Online] Available at: http://allocator.sourceforge.net/ (Accessed November 21, 2008) Ronell, M. (May 29, 2006) - "GCC Bugzilla Bug 21251 - Placement into shared memory" - GNU - [Online] Available at: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=21251 (Accessed November 21, 2008)
53
SGI (2006) - "Standard Template Library Programmer's Guide - Introduction to the Standard Template Library" - Silicon Graphics, Inc. - Hewlett-Packard Company - [Online] Available at: http://www.sgi.com/tech/stl/stl_introduction.html (Accessed September 25, 2008) Stepanov A. & Lee M. (1995) - "The standard template library" - Hewlett-Packard Company - [Online] Available at: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.7776&rep=rep1&type=pdf (Accessed December 8, 2008)
Stroustrup, B. (2000) - "The C++ Programming Language" - Addison Wesley - ISBN 0-201-70073-5
54
Appendix A. SHARED CONTAINER LIBRARY PROGRAMMERS’
MANUAL (EXCERPTS)
The latest version of the programmer’s documentation for the shared container library is available online at: http://bergeon.francois.perso.neuf.fr/containers/doc.html.
Shared heap parameters need to be defined in the code in order to be present at link time. Default values are defined in the header heap_paramters.h that can be included in the source code or modified to suit.
char* _heapname
_heapname is an arbitrary name assigned to the shared heap. The default value is “SHARED_CONTAINERS”.
size_t _heapsize
_heapsize is the size of the shared heap in bytes. The default value is 10,485,760 bytes or 10 Mbytes.
size_t _chunksize
_chunksize is the size of each allocation chunk in bytes. The default value is 32 bytes.
void* _baseaddress
_baseaddress is the address in the process address space where the shared heap is to be mapped. The default value is 0x01100000, an arbitrary high offset in the process space.
Definition
These values are defined in the header heap_parameters.h.
56
A.3 sync
Description
sync is a synchronization class designed to be used as-is or derived by classes that require synchronization. sync offers both exclusive (write) locks and non-exclusive (read) locks. Lock behavior follows the following matrix:
For A corresponding call to read_unlock is required for each call to read_lock. If the same thread calls write_lock more than once, the operation succeeds. A corresponding call to write_unlock is required for each successful call to write_lock. A call to upgrade_lock is functionally equivalent to a thread-safe call to read_unlock followed by a call to write_lock. Upgrade from a non-exclusive lock to an exclusive lock cannot be guaranteed. A call to downgrade_lock is functionally equivalent to a thread-safe call to write_unlock followed by a call to read_lock. Downgrade from an exclusive lock to non-exclusive lock is always guaranteed. A call to is_locking_thread returns true if the calling thread and process is the current owner of the exclusive lock. The timeout parameter is rounded down to the closest increment of 10ms. A value of less than 10 returns immediately. A value of -1 is equivalent to an infinite timeout. An infinite timeout should never be used in a call to upgrade due to the risk of a possible race condition.
Example
sync s; s.read_lock(); … if (s.upgrade_lock()) s.write_unlock(); else s.read_unlock();
Definition
Defined in the header sync.h.
Request\Existing lock none read lock write lock read_lock success success wait for release of write lock
write_lock success wait for release of all read locks wait for release of write lock
upgrade_lock n/a wait for release of all other read locks n/a
downgrade_lock n/a n/a success is_locking_thread false false true if thread owns the lock
57
Members
Member Description bool read_lock(long timeout = -1) Obtain non-exclusive read lock within timeout milliseconds,
0 for immediate or -1 for infinite. Returns true if lock obtained.
for immediate or -1 for infinite. Returns true if lock obtained.void write_unlock() Release write lock bool upgrade_lock(long timeout = 0)
Attempt to upgrade an existing non-exclusive read lock to an exclusive write lock within timeout milliseconds or 0 for immediate. Returns true if the lock was successfully upgraded. If the upgrade fails, the non-exclusive read lock is not released.
void downgrade_lock() Downgrade from an existing exclusive write lock to a non-exclusive read lock.
58
A.4 shared_vector<T >
Description
A shared_vector is a shared memory implementation of a STL vector container. shared_vector offers the same functionalities and properties than vector. Please refer to the STL documentation for information on the vector class. shared_vector also offers synchronization methods that should be used if other processes are susceptible to access it concurrently. shared_vector objects are be created, attached to and released from by calling the static methods of the SharedVectorFactory class.
Parameter Description Default T The shared vector's value type: the type of object that is stored in the vector.
Type requirements
Same requirements than for vector.
Public base classes
sync
Members
All public members of the vector class and of the sync class are exposed.
59
A.5 shared_stack<T, Sequence >
Description
A shared_stack is a shared memory implementation of a STL stack adapter. A shared_stack offers the same functionalities and properties than the stack. Please refer to the STL documentation for information on the stack class. shared_stack also offers synchronization methods that should be used before accessing the container if other processes are susceptible to access it concurrently. shared_stack objects are created, attached to and released from by calling the static methods of the SharedStackFactory class.
Parameter Description Default T The shared deque's value type: the type of object that is stored
in the deque.
Sequence The type of the underlying container used to implement the stack.
shared_deque<T>
Type requirements
60
Same requirements than for stack.
Public base classes
sync
Members
All public members of the stack class and of the sync class are exposed.
61
A.6 shared_map <Key, Data, Compare>
Description
A shared_map is a shared memory implementation of a STL map container. A shared_map offers the same functionalities and properties than the map. Please refer to the STL documentation for information on the map class. shared_map also offers synchronization methods that should be used before accessing the container if other processes are susceptible to access it concurrently. shared_map objects are created, attached to and released from by calling the static methods of the SharedMapFactory class.
Parameter Description Default Key The map's key type and value type. This is also defined as shared _map::key_type Data The map's data type. This is also defined as shared_map::data_type. Compare The key comparison function, a Strict Weak Ordering whose argument type is
key_type; it returns true if its first argument is less than its second argument, and false otherwise. This is also defined as shared_map::key_compare
less<Key>
Type requirements
Same requirements than for map.
Public base classes
sync
Members
All public members of the map class and of the sync class are exposed.
63
A.7 shared_hash_set <Key, Compare>
Description
A shared_hash_set is a shared memory implementation of a STL hash_set container. A shared_hash_set offers the same functionalities and properties than the hash_set. Please refer to the STL documentation for information on the hash_set class. shared_hash_set also offers synchronization methods that should be used before accessing the container if other processes are susceptible to access it concurrently. shared_hash_set objects are created, attached to and released from by calling the static methods of the SharedHash_setFactory class.
Definition
Defined in the header shared_hash_set.h.
Template parameters
Parameter Description Default Key The set's key type and value type. This is also defined as shared
_hash_set::key_type and shared_hash_set::value_type
Compare The key comparison function, a Strict Weak Ordering whose argument type is key_type; it returns true if its first argument is less than its second argument, and false otherwise. This is also defined as shared_hash_set::key_compare and shared_hash_set::value_compare.
less<Key>
Type requirements
Same requirements than for hash_set.
Public base classes
sync
Members
All public members of the hash_set class and of the sync class are exposed.
64
A.8 SharedVectorFactory<T >
Description
SharedVectorFactory is a static class used to create, attach to or detach from a shared_vector. Shared vectors are named entities to be identifiable by other processes.
Example
Process 1: shared_vector<int> *V = SharedVectorFactory<int>::create(“name”); Process 2: shared_vector<int> *V = SharedVectorFactory<int>::attach(“name”); ... SharedVectorFactory<int>::detach(“name”); Process 1: SharedVectorFactory<int>::detach(“name”);
Definition
Defined in the header shared_vector.h.
Template parameters
Parameter Description T The shared vector's value type: the type of object that is stored in the vector.
Type requirements
Same requirements than for vector.
Members
Member Description
shared_vector* attach(char* name) Attach to an existing shared_vector named name
shared_vector* create(char* name) Create new shared_vector named name
shared_vector* create(char* name, shared_vector&)
Copy constructor
shared_vector* create(char* name, size_t n)
Create new shared_vector named name with n elements
shared_vector* create(char* name, size_t n, const T& t)
Create new shared_vector named name with n copies of t
void detach(char* name) Detach from shared_vector named name
65
A.9 SharedStackFactory<T , Sequence>
Description
SharedStackFactory is a static class used to create, attach to or detach from a shared_stack. Shared stacks are named entities to be identifiable by other processes.
Example
Process 1: shared_stack<int> *S = SharedStackFactory<int>::create(“name”); Process 2: shared_stack<int> *S = SharedStackFactory<int>::attach(“name”); ... SharedStackFactory<int>::detach(“name”); Process 1: SharedStackFactory<int>::detach(“name”);
Definition
Defined in the header shared_stack.h.
Template parameters
Parameter Description Default T The shared stack's value type: the type of object that is stored
in the stack.
Sequence The type of the underlying container used to implement the stack.
shared_deque<T>
Type requirements
Same requirements than for stack.
Members
Member Description
shared_stack* attach(char* name) Attach to an existing shared_stack named name
shared_stack* create(char* name) Create new shared_stack named name
shared_stack* create(char* name, shared_stack&)
Copy constructor
shared_stack* create(char* name, Create new shared_stack named
66
size_t n) name with n elements shared_stack* create(char* name, size_t n, const T& t)
Create new shared_stack named name with n copies of t
void detach(char* name) Detach from shared_stack named name
67
A.10 SharedMapFactory map<Key, Data, Compare>
Description
SharedMapFactory is a static class used to create, attach to or detach from a shared_map. Shared maps are named entities to be identifiable by other processes.
Example
Process 1: shared_map<std::string, int> *S = SharedMapFactory<std::string, int>::create(“name”); Process 2: shared_map<std::string, int> *S = SharedMapFactory<std::string, int>::attach(“name”); ... SharedMapFactory<std::string, int>::detach(“name”); Process 1: SharedMapFactory<std::string, int>::detach(“name”);
Definition
Defined in the header shared_map.h.
Template parameters
Parameter Description Default Key The map's key type and value type. This is also defined as shared
_map::key_type and shared_map::value_type
Data The map's data type. This is also defined as shared_map::data_type.
Compare The key comparison function, a Strict Weak Ordering whose argument type is key_type; it returns true if its first argument is less than its second argument, and false otherwise. This is also defined as shared_map::key_compare and shared_map::value_compare.
less<Key>
Type requirements
Same requirements than for map.
Members
Member Description
shared_map* attach(char* name) Attach to an existing shared_map named name
shared_map* create(char* name) Create new shared_map named name
Creates a shared_map named name with a copy of a range, using comp as the key_compare object.
shared_map* create(char* name, shared_map&)
Copy constructor
void detach(char* name) Detach from shared_map named name
69
A.11 SharedHashSetFactory set<Key, Compare>
Description
SharedHashSetFactory is a static class used to create, attach to or detach from a shared_hash_set. Shared hash sets are named entities to be identifiable by other processes.
Example
Process 1: shared_hash_set<int> *S = SharedHashSetFactory<int>::create(“name”); Process 2: shared_hash_set<int> *S = SharedHashSetFactory<int>::attach(“name”); ... SharedHashSetFactory<int>::detach(“name”); Process 1: SharedHashSetFactory<int>::detach(“name”);
Definition
Defined in the header shared_hash_set.h.
Template parameters
Parameter Description Default Key The set's key type and value type. This is also defined as shared
_hash_set::key_type and shared_hash_set::value_type
Compare The key comparison function, a Strict Weak Ordering whose argument type is key_type; it returns true if its first argument is less than its second argument, and false otherwise. This is also defined as shared_hash_set::key_compare and shared_hash_set::value_compare.
less<Key>
Type requirements
Same requirements than for hash_set.
Members
Member Description shared_hash_set* attach(char* name)
void detach(char* name) Detach from shared_hash_set named name
71
Appendix B. SHARED CONTAINERS LIBRARY SOURCE CODE
(EXCERPTS)
The latest version of the source code for the shared container library is available online at: http://bergeon.francois.perso.neuf.fr/containers/src.zip
B.1 safe_counter.h /* * safe_counter.h - Thread-safe counters * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include <windows.h> /* * Wrapper class around an interlocked counter * - Class is platform dependent. */ class safe_counter { private: // Counter is volatile and needs to be aligned // on a 32bit boundary for interlocked operations _declspec(align(32)) volatile LONG _counter; public: // Constructor - no need for interlocked access at this time inline safe_counter(long n = 0) : _counter(n) {}; // Assignment operator inline safe_counter& operator=(long n)
{ InterlockedExchange(&_counter, n); return *this; }; // Cast operator to const long inline operator const long()
{ return _counter; }; // Increment and decrement operators inline long operator++()
{ return InterlockedIncrement(&_counter); }; inline long operator--()
{ return InterlockedDecrement(&_counter); }; };
Listing 4: safe_counter.h
72
B.2 sync.h /* * sync.h - Simple read/write locking mechanism * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include "safe_counter.h" /* * sync class - implements a synchronization for shared containers * - Class is platform dependent. */ class sync { private: // Internal read/write sempahores safe_counter _readlock; safe_counter _writelock; long _lockingthreadid; long _lockingprocessid; // Standard loop delay is 10ms static const unsigned DELAY = 10;
73
public: // Obtain non-exclusive read lock with timeout // - More than one read lock is allowed at any single time // timeout - time out in milliseconds, -1 for infinite (default), 0 for no wait // bool read_lock(long timeout = -1); // Release non-exclusive read lock void read_unlock(); // Obtain exclusive write lock with timeout // - No read locks and only one write lock
// is allowed at any single time // timeout - time out in milliseconds, -1 for infinite (default), 0 for no wait // inline bool write_lock(long timeout = -1) { return write_lock(timeout, 0); };
// Check if the current thread owns the write lock bool is_locking_thread(); // Release exclusive write lock void write_unlock(); // Upgrade from a non-exclusive read lock to an
// exclusive write lock // - Thread must already own a read lock // - If another thread is already waiting for a wite lock it will be preempted // timeout - time out in milliseconds, 0 for no wait (default) // bool upgrade_lock(long timeout = 0); // Downgrade from an exclusive write lock to a non-exclusive read lock // - Thread must already own a write lock // - Read lock is guaranteed // void downgrade_lock(); private:
74
// Internal write lock with acceptable number of existing locks bool write_lock(long timeout, long existing); // Get thread and process id inline long get_threadid() { return GetCurrentThreadId(); } inline long get_processid() { return GetCurrentProcessId(); }
};
Listing 5: sync.h
B.3 sync.cpp /* * sync.cpp - Simple read/write locking mechanism * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #include "sync.h" // Obtain non-exclusive read lock with timeout // - More than one read lock is allowed at any single time // timeout - time out in milliseconds, 0 for infinite, 1 for no delay //
75
bool sync::read_lock(long timeout) { // Obtain maximum number of iterations to run long n = (timeout / DELAY) + 1; for (;;) { // Check that no write lock has been requested if (_writelock == 0) { // Obtain read lock ++_readlock; // Has no write lock been requested in the meantime? if (_writelock == 0) return true; // Release read lock and try again --_readlock; } // Timeout expired? if (timeout >= 0 && --n == 0) return false; // Wait Sleep(DELAY); } }; // Release non-exclusive read lock void sync::read_unlock() { // Safety if (--_readlock == -1) ++_readlock; }
76
// Obtain exclusive write lock with timeout // - No read locks and only one write lock is allowed at any single time // timeout - time out in milliseconds, 0 for infinite, 1 for no delay // existing - acceptable number of existing locks (0:normal, 1:upgrade) // bool sync::write_lock(long timeout, long existing) { // Has this thread already obtained an exclusive write lock? if (is_locking_thread()) { // Increment lock counter so that the next call to // write_unlock() does not release the lock ++_writelock; return true; } // Obtain maximum number of iterations to run long n = (timeout / DELAY) + 1; for (;;) { // Try to obtain an exclusive write lock // or preempt other threads if we are upgrading // from a non-exclusive one while (++_writelock > 1 && existing == 0) { --_writelock; // Timeout expired? if (timeout >= 0 && --n == 0) return false; // Wait Sleep(DELAY); } // Then wait for all readers to release their non-exlusive read locks while (_readlock > existing)
77
{ // Timeout expired? if (timeout >= 0 && --n == 0) { // Release write lock --_writelock; return false; } // Wait Sleep(DELAY); } // Write lock obtained, store locking thread id _lockingthreadid = get_threadid(); _lockingprocessid = get_processid(); return true; } }; // Check if the current thread owns the write lock bool sync::is_locking_thread() { return (_writelock > 0 && _lockingthreadid == get_threadid() &&
_lockingprocessid == get_processid()); } // Release exclusive write lock void sync::write_unlock() { long n = --_writelock; // Safety if (n == -1) ++_writelock; // Reset thread and process id if (n == 0) _lockingthreadid = _lockingprocessid = -1; }
78
// Upgrade from non-exclusive read lock to exclusive write lock // - Thread must already own a read lock // - If another thread is already waiting for a wite lock it will be preempted // - An infinite timeout may lead to a running condition // timeout - time out in milliseconds, 0 for no wait // bool sync::upgrade_lock(long timeout) { // Obtain a write lock with one existing read lock (ours) if (!write_lock(timeout, 1)) return false; // Release our read lock read_unlock(); // Lock upgraded return true; } // Downgrade from an exclusive write lock to a non-exclusive read lock // - Thread must already own a write lock // - Read lock is guaranteed // void sync::downgrade_lock() { ++_readlock; write_unlock(); }
Listing 6: sync.cpp
79
B.4 heap_parameters.h /* * heap_parameters.h - Parameters for shared heap * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #include <stddef.h> // These variables can be modified to suit specific needs char* _heapname = "SHARED_CONTAINERS"; // Arbitrary heap name size_t _heapsize = 67108864; // 64Mb size_t _chunksize = 64; // 64 bytes void* _baseaddress = (void*)0x01100000; // Arbitrary high for debug
Listing 7: heap_parameters.h
B.5 shared_heap.h /* * shared_heap.h - Heap implementation in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ----
80
* * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #ifdef WIN32 #include <windows.h> #endif /* * shared_heap class - implements a heap in shared memory * * This class is platform dependent. * */ class shared_heap { private: class shared_header; // Forward class declaration // Class members shared_header* _header; // Pointer to shared header void* _heap; // Address of shared heap long _lasterror; // Last error caught #ifdef WIN32 HANDLE _mapping; // Handle of Win32 mapping object #endif // Standard loop delay is 20ms static const unsigned DELAY = 20;
B.6 shared_heap.cpp /* * shared_heap.cpp - Heap implementation in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a
82
* Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * * This class implements a heap of segregated storage in shared memory. * It uses WIN32 primitives and is platform dependent. * */ #include "shared_heap.h" #include "safe_counter.h" #ifndef BYTE #define BYTE unsigned char #endif // The signature is used to confirm that an existing memory // mapping has been created by this same code #define SHAREDHEAP_SIGNATURE "**FBSH**" // Indicator of last chunk in chunk list #define LAST_CHUNK ((LPVOID)~0) // Macro to roundup value #define ROUNDUP(a,b) ((a)%(b)==0?(a):((((a)/(b))+1)*(b))) // // The shared header is located at the beginning of the shared memory mapped segment // All processes accessing shared containers rely on the shared header to map // shared memory to the same virtualized memory space and to access existing containers // __declspec(align(32)) volatile class shared_heap::shared_header { public: char _signature[sizeof(SHAREDHEAP_SIGNATURE)]; // Signature that identifies view as a shared heap void* _baseaddress; // Common base address to be used by all processes void* _head; // Pointer to first available chunk size_t _heapsize, _chunksize; // Size of heap and chunk size
83
long _longparam; // Client long parameter void* _ptrparam; // Client pointer parameter safe_counter _numprocesses; // Number of processes currently accessing shared memory safe_counter _allocating; // 1 if allocation in progress, 0 otherwise char _filename[MAX_PATH]; // Temp filename }; // // Shared heap constructor // // heapname - name of shared heap // heapsize - size of shared heap in bytes (defaults to 0x100000 = 1Mb) // chnksize - size of segregated storage chunks (defauts to 0x1000 = 4Kb) // baseaddress - address of shared mempory mapping of NULL if default // shared_heap::shared_heap(const char* heapname, size_t heapsize, size_t chunksize, void* baseaddress) { // Flag for creation of new shared heap BOOL bCreateNew = FALSE; // Size of shared header is rounded up to proper allocation granularity SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); DWORD dwHeaderSize = ROUNDUP(sizeof(shared_header), SystemInfo.dwAllocationGranularity); // Buffer for temp filename and wide char heap name TCHAR szTmpFile[MAX_PATH]; TCHAR szHeapName[MAX_PATH]; // Convert heap name to wide char for Win32 API calls MultiByteToWideChar(CP_ACP, 0, heapname, -1, szHeapName, MAX_PATH); // Initialize members _lasterror = 0; _mapping = NULL; _header = NULL; _heap = NULL;
84
try { // Open file mapping _mapping = OpenFileMapping(FILE_MAP_WRITE, // RW access FALSE, // Handle cannot be inherited szHeapName); // Name of file mapping object // Obtain last system error _lasterror = GetLastError(); // Unable to open file mapping, attempt to create temp file if (_mapping == NULL && _lasterror == ERROR_FILE_NOT_FOUND) { // Create file mapping bCreateNew = TRUE; // Check requested heap size if (heapsize == NULL) { SetLastError(ERROR_BAD_LENGTH); throw; } // Create temporary file for mapping TCHAR szTmpPath[MAX_PATH-14]; // Get temp dir & file GetTempPath(MAX_PATH-14, szTmpPath); GetTempFileName(szTmpPath, szHeapName, 0, szTmpFile); // Create temp file HANDLE hFile = CreateFile(szTmpFile, // Temp file name GENERIC_WRITE | GENERIC_READ, // RW Access FILE_SHARE_WRITE, // Share mode NULL, // Optional security attributes CREATE_ALWAYS, // Create file FILE_ATTRIBUTE_TEMPORARY, // Temporary file NULL); // Flags & attributes // Obtain last system error _lasterror = GetLastError();
85
// Exit if we could not create temp file if (hFile == INVALID_HANDLE_VALUE) throw; // Add header size to desired size __int64 qwMaxSize = heapsize + dwHeaderSize; DWORD dwSizeHigh = (qwMaxSize & 0xFFFFFFFF00000000) >> 32; // Hi dword DWORD dwSizeLow = (qwMaxSize & 0x00000000FFFFFFFF); // Low dword // Create new file mapping object _mapping = CreateFileMapping(hFile, // Handle to temp file NULL, // Optional security attributes PAGE_READWRITE, // Page protection dwSizeHigh, // Hi-order DWORD of size dwSizeLow, // Low-order DWORD of size szHeapName); // Name of file mapping object // Obtain last system error _lasterror = GetLastError(); // Close underlying temp file CloseHandle(hFile); } // Exit function if OpenFileMapping or CreateFileMapping failed if (_mapping == NULL) throw; // Map view of file to obtain/create header _header = (shared_header *)MapViewOfFileEx(_mapping, // File mapping object FILE_MAP_WRITE, // RW access 0, 0, // Offset 0 dwHeaderSize, // Header size NULL); // Any base address // Unable to map view of file: cleanup & exit if (_header == NULL) { _lasterror = GetLastError(); throw;
86
} if (bCreateNew) { // Map view of file to target base address _heap = MapViewOfFileEx(_mapping, // File mapping object FILE_MAP_WRITE, // RW access 0, dwHeaderSize, // Offset (must be aligned to allocation granularity) 0, // Full size baseaddress); // Target base address or NULL // Save last error if failed _lasterror = GetLastError(); if (_heap == NULL) throw; // Populate header _header->_baseaddress = _heap; // Now common base address _header->_head = LAST_CHUNK; // Storage not initialized _header->_heapsize = heapsize; // Heap size _header->_chunksize = max(chunksize, sizeof(void**)); // Chunk size _header->_longparam = 0; // Set client DWORD parameter _header->_ptrparam = NULL; // Set client LPVOID parameter _header->_numprocesses = 1; // Currently 1 process _header->_allocating = 0; // No allocation currently in progress WideCharToMultiByte(CP_ACP, 0, szTmpFile, -1, _header->_filename, MAX_PATH, NULL, NULL); // Convert & store temp filename // Initialize segregated storage deallocate(_heap, heapsize); // Insert signature last to prevent race conditions with other processes strcpy_s(_header->_signature, sizeof(SHAREDHEAP_SIGNATURE), SHAREDHEAP_SIGNATURE); // Signature } else { // Check signature or fail if (strcmp(_header->_signature, SHAREDHEAP_SIGNATURE) != 0) { _lasterror = ERROR_BAD_FORMAT; throw;
87
} // Increment number of client processes if (++_header->_numprocesses == 1) { // A process is currently closing the shared memory _lasterror = ERROR_FILE_NOT_FOUND; throw; } // Map view of file to target base address _heap = MapViewOfFileEx(_mapping, // File mapping object FILE_MAP_WRITE, // RW access 0, dwHeaderSize, // Offset (must be aligned to allocation granularity) 0, // Full size _header->_baseaddress); // Common base address // Save last error if failed _lasterror = GetLastError(); if (_heap == NULL) throw; } } catch (...) { // Cleanup cleanup(); // Re-set error code SetLastError(_lasterror); } } // Standard destructor shared_heap::~shared_heap(void) { cleanup(); } // // Cleanup is called by destructor or
88
// if an exception is caught in the constructor // void shared_heap::cleanup() { // Buffer for temp filename TCHAR szTmpFile[MAX_PATH]; // Initialize as an empty string szTmpFile[0] = '\0'; // Unmap heap if (_heap != NULL) UnmapViewOfFile(_heap); // Unmap header if (_header != NULL) { // Are we the last process to use this shared memory file? if (--_header->_numprocesses == 0) { // If so, convert temp filename so we can delete it MultiByteToWideChar(CP_ACP, 0, _header->_filename, -1, szTmpFile, MAX_PATH); } // Unmap header UnmapViewOfFile((LPVOID)_header); } // Close file mapping if (_mapping != NULL) CloseHandle(_mapping); // Reset member variables _heap = NULL; _header = NULL; _mapping = NULL; // Delete temp file if we are the last user process
89
if (szTmpFile[0] != '\0') DeleteFile(szTmpFile); } // // Allocate memory // // size - number of bytes to allocate // void* shared_heap::allocate(size_t size) { // Synchronization lock(); // Out of memory? if (_header->_head == LAST_CHUNK) { unlock(); return NULL; } // Roundup allocation size to a multiple of chunk size size = ROUNDUP(size, _header->_chunksize); // Special case: allocation of a single chunk if (size == _header->_chunksize) { // Obtain head chunk void** p = (void**)_header->_head; // Move head to point to next chunk _header->_head = *p; // Return former head chunk unlock(); return (void*)p; } // Number of contiguous chunks to allocate if > 1
90
unsigned chunks = size / _header->_chunksize; // Start at head chunk and look for contiguous block of chunks void* head = _header->_head; // Start of contiguous block void** previous = &_header->_head; // Pointer to previous chunk // Loop do { // Loop variables void** p; // Index pointer to chunks unsigned n; // Counter for contiguous chunks // Loop through contiguous chunks for (p = (void**)head, n = 1; *p == ((BYTE*)p + _header->_chunksize); p = (void**)*p, ++n) { // Return if we found enough contiguous chunks if (n == chunks) { // Set chunk before block to point to next chunk *previous = *p; // Return start of chunk block unlock(); return head; } } // Continue searching from next chunk forward previous = p; head = *p; // Stop when we reach the end of the heap } while (head != LAST_CHUNK); // No contiguous chunks found, allocation failed unlock(); return NULL; } // // Deallocate buffer - can also be used to initialize segregated
91
// storage for the entire heap // // head - pointer to buffer // size - buffer size // void shared_heap::deallocate(void* head, size_t size) { // Synchronization lock(); // Roundup size to an integer number of chunks size = ROUNDUP(size, _header->_chunksize); // Calculate address of last chunk to be deallocated void** toe = (void**)((BYTE*)head + size - _header->_chunksize); // Loop variables void** previous = NULL; // Address of previous chunk void* p; // Index pointer to chunks // Go through all chunks from head to toe for (p = head; p <= toe; p = ((BYTE*)p + _header->_chunksize)) { // Update previous chunk so it points to current chunk if (previous != NULL) *previous = p; // Store address of new previous chunk previous = (void**)p; } // Make last chunk point to previous head *toe = _header->_head; // Make head point to first chunk _header->_head = head; unlock(); } //
92
// Obtain client parameters from shared header // // longparam - pointer to long parameter // ptrparam - pointer to pointer parameter // void shared_heap::get_params(long** longparam, void*** ptrparam) { *longparam = &_header->_longparam; *ptrparam = &_header->_ptrparam; } // Lock shared heap. Wait if busy. No timeout void shared_heap::lock() { if (_header != NULL) { while (++_header->_allocating > 1) { if (--_header->_allocating == 0) continue; Sleep(DELAY); } } } // Unlock shared heap void shared_heap::unlock() { if (_header != NULL) --_header->_allocating; }
Listing 9: shared_heap.cpp
93
B.7 shared_pool.h /* * shared_pool.h - Object allocation in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include <map> #include <string> #include "shared_heap.h" #include "shared_allocator.h" #include "sync.h" /* * shared_map class * - derives from the map and sync classes * - implements the map class with the shared_allocator template parameter */ template<class Key, class Data, class Compare=std::less<Key>> class shared_map : public sync, public std::map<Key, Data, Compare, shared_allocator<std::pair<const Key, Data>>> {}; /* * Shared pool class - Provides singleton allocation interface * to shared heap and manages map of shared objects * */ class shared_pool
94
{ private: // shared_object class describes each shared objects class shared_object; // Map of objects stored in shared memory typedef shared_map<std::string, shared_object*> container_map; // Shared heap and map of shared objects static shared_heap* _heap; static container_map* _containermap; public: // Allocator interface to shared heap static void* allocate(size_t size); static void deallocate(void* buffer, size_t size); static size_t max_size(); // Insert, retrieve and release objects in shared map static bool insert(char *name, void* object, size_t size); static void* retrieve(char *name, size_t size); static void* release(char *name); private: // Lazy initialization static void init(); };
* --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ // Force Microsoft VC++ 6 to turn off iterator checking #ifdef WIN32 #define _HAS_ITERATOR_DEBUGGING 0 #define _SECURE_SCL 0 #endif #include "shared_pool.h" // External parameters used to construct a shared heap // These variables can be modified to suit specific needs extern char* _heapname; // Arbitrary heap name extern size_t _heapsize; // 10Mb extern size_t _chunksize; // 32 bytes extern void* _baseaddress; // Arbitrary high for debug // Class for shared object stored in container map class shared_pool::shared_object { public: void* _object; // Pointer to object size_t _size; // Size of object safe_counter _refcount; // Usage count shared_object(void* object, size_t size): _object(object), _size(size), _refcount(1) {};
96
shared_object() {}; ~shared_object() {}; }; // Initialize static members shared_heap* shared_pool::_heap = NULL; shared_pool::container_map* shared_pool::_containermap = NULL; // // Allocate buffer from shared heap // // size - number of bytes to allocate // void* shared_pool::allocate(size_t size) { // Perform lazy intialization if shared heap is not initialized if (_heap == NULL) init(); // Allocate memory and return return _heap->allocate(size); } // // Deallocate buffer // // head - pointer to buffer // size - buffer size // void shared_pool::deallocate(void* buffer, size_t size) { // Shared heap must be initialized if (_heap == NULL) return; // Deallocate buffer _heap->deallocate(buffer, size);
97
}; // // Return heap size // size_t shared_pool::max_size() { return _heapsize; } // // Insert shared object in container map // // name - name of object // object - pointer to object // size - size of object // bool shared_pool::insert(char *name, void* object, size_t size) { // Perform lazy intialization if shared heap is not initialized if (_heap == NULL) init(); // Look for entry - obtain non-exclusive lock first // Return failure if a container with the same name is already present in the shared container map std::string s(name); _containermap->read_lock(); if (_containermap->find(s) != _containermap->end()) { // Release read lock and return failure _containermap->read_unlock(); return false; } // Allocate and build new shared object void* p = allocate(sizeof(shared_object)); if (p == NULL)
98
{ // Release read lock and return failure _containermap->read_unlock(); return false; } shared_object* so = new(p) shared_object(object, size); // Upgrade to an exclusive lock if (!_containermap->upgrade_lock()) { // If upgrade failed we have to do it manually // First release the read lock - this may give another process a write lock _containermap->read_unlock(); // Then obtain a write lock the old fashioned way _containermap->write_lock(); // Check for name again in case it was inserted concurrently if (_containermap->find(s) != _containermap->end()) { // Release write lock and deallocate object, return failure _containermap->write_unlock(); deallocate(p, sizeof(shared_object)); return false; } } // Insert shared object and release lock (*_containermap)[s] = so; _containermap->write_unlock(); // Return success return true; } // // Retrieve shared object from container map //
99
// name - name of object // size - size of object // void* shared_pool::retrieve(char *name, size_t size) { // Perform lazy intialization if shared heap is not initialized if (_heap == NULL) init(); // Look for entry - obtain non-exclusive lock _containermap->read_lock(); std::string s(name); container_map::iterator it = _containermap->find(s); // Return NULL if no container with that name is present in the shared container map if (it == _containermap->end()) { // Release read lock and return failure _containermap->read_unlock(); return NULL; } // Obtain pointer to shared object and release read lock shared_object* so = it->second; _containermap->read_unlock(); // Check shared object and return NULL if size does not match if (so == NULL || so->_size != size) return NULL; // Increment refcount and return pointer to object ++so->_refcount; return so->_object; } // // Release object by decrementing its refcount. If refcount reaches zero,
100
// return pointer to object so its destructor can be called. // // name - name of object // void* shared_pool::release(char *name) { // Shared heap must be initialized if (_heap == NULL) return NULL; // Look for entry - obtain non-exclusive lock first std::string s(name); _containermap->read_lock(); container_map::iterator it = _containermap->find(s); // Return NULL if no container with that name is present in the shared container map if (it == _containermap->end()) { // Release read lock and return failure _containermap->read_unlock(); return NULL; } // Obtain pointer to shared object and decrement refcount shared_object* so = it->second; if (so == NULL || --so->_refcount > 0) { // Release read lock and return NULL if object still in use _containermap->read_unlock(); return NULL; } // // refcount == 0 - discard object // // Retain pointer to object
101
void* p = so->_object; // Upgrade to an exclusive lock if (!_containermap->upgrade_lock()) { // If upgrade failed we have to do it manually // First release the read lock - this may give another process a write lock _containermap->read_unlock(); // Then obtain a write lock the old fashioned way _containermap->write_lock(); // Get shared object again in case it was removed or attached concurrently if (_containermap->find(s) == _containermap->end() || so->_refcount > 0) { // Return NULL if object not found or still in use _containermap->write_unlock(); return NULL; } } // Erase map entry and release lock _containermap->erase(it); _containermap->write_unlock(); // Deallocate object deallocate(so, sizeof(shared_object)); // Return pointer to object for destruction return p; } // // Lazy initialization of shared heap // void shared_pool::init() { // Pointers to shared client parameters
102
long* longparam; void** ptrparam; // Build new shared heap from external parameters _heap = new shared_heap(_heapname, _heapsize, _chunksize, _baseaddress); // Obtain pointers to client parameters in shared header _heap->get_params(&longparam, &ptrparam); // Create container map if not initialized if (*ptrparam == NULL) { _containermap = new(_heap->allocate(sizeof(container_map))) container_map; // Store pointer to map in pParam *ptrparam = _containermap; } else { // Or attach to existing container map _containermap = (container_map*)*ptrparam; } }
Listing 11: shared_pool.cpp
B.9 shared_allocator.h /* * shared_allocator.h - STL allocator in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY
103
* * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include "shared_pool.h" class shared_pool; template<class T> class shared_allocator { public: typedef T value_type; typedef unsigned int size_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; pointer address(reference r) const { return &r; } const_pointer address(const_reference r) const {return &r;} shared_allocator() throw() {}; template<class U> shared_allocator(const shared_allocator<U>& t) throw() {}; ~shared_allocator() throw() {}; // space for n Ts pointer allocate(size_t n, const void* hint=0) { return(static_cast<pointer> (shared_pool::allocate(n*sizeof(T)))); }
B.10 container_factories.h /* * containers_factories.h - STL containers in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include "shared_pool.h" // Placement new in the shared pool #define SHARED_NEW new(shared_pool::allocate(sizeof(container_type))) /* * SharedContainerFactory - parameterized abstract factory class * - Used to create new shared containers or attach to existing ones. * - Methods of this class are static and call static methods of shared_pool. * - Class is meant to be derived into specific shared container factory classes. */ template<class C> class _SharedContainerFactory { public: typedef C container_type; // Container's type - this type is available to all derived classes // Create new shared container and assign specified name // name - name of shared container // static container_type* create(char* name)
106
{ // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(); // Add container to shared pool and return return insert_container(name, c); }; // Create new shared container with copy constructor // name - name of shared container // cont - container to copy from // static container_type* create(char* name, const container_type& cont) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(cont); // Add container to shared pool and return return insert_container(name, c); }; // Attach to existing shared container by name // name - name of shared container // static container_type *attach(char* name) { // Retrieve container from shared pool return (container_type*)shared_pool::retrieve(name, sizeof(container_type)); }; // Detach from shared container - release if last user // name - name of shared container // static void detach(char* name) { // Detach from container in sahred pool container_type* c = (container_type*)shared_pool::release(name); // shared_pool returns pointer to the container if we are the last user, NULL otherwise if (c != NULL)
107
destroy_container(c); } protected: // Attempt to insert container in shared pool // name - name of shared container // c - pointer to shared container // static container_type* insert_container(char* name, container_type *c) { // Add container to shared pool - destroy it and return NULL if insertion failed if (!shared_pool::insert(name, c, sizeof(container_type))) { destroy_container(c); return NULL; } // Returned newly created container return c; } // Destroy container and deallocate // c - pointer to shared container // static void destroy_container(container_type* c) { // Call destructor on shared container c->~container_type(); // Deallocate memory used by shared container object shared_pool::deallocate(c, sizeof(container_type)); }; }; /* * SharedSequenceContainerFactory - parameterized abstract factory class * - Specialization of the SharedContainerFactory class for sequence containers. * - Implements the pre-allocation and replication constructors offered * by sequence containers.
108
*/ template<class C> class _SharedSequenceContainerFactory : public _SharedContainerFactory<C> { public: typedef typename container_type::value_type value_type; // Container's value type typedef typename container_type::size_type size_type; // Container's size_type // Redefinition of base class static create methods static container_type* create(char* name)
{ return _SharedContainerFactory<container_type>::create(name, c); }; // Create new shared container with pre-allocation // name - name of shared container // n - number of objects to pre-allocate // static container_type* create(char* name, size_type n) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(n); // Add container to shared pool and return return insert_container(name, c); }; // Create new shared container with replication // name - name of shared container // n - number of objects to replicate // t - object to replicate // static container_type* create(char* name, size_type n, value_type& t) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(n, t); // Add container to shared pool and return return insert_container(name, c);
109
}; }; /* * SharedAssociativeContainerFactory - parameterized abstract factory class * - Specialization of the SharedContainerFactory class for associative containers. * - Implements constructors with copy of range and specified key_compare function * offered by associative containers. */ template<class C> class _SharedAssociativeContainerFactory : public _SharedContainerFactory<C> { public: typedef typename container_type::key_compare key_compare; // Container's key_compare function // Redefinition of base class static create methods static container_type* create(char* name)
{ return _SharedContainerFactory<container_type>::create(name); }; static container_type* create(char* name, container_type& c)
{ return _SharedContainerFactory<container_type>::create(name, c); }; // Create new shared container with key_compare object // name - name of shared container // comp - key_compare object // static container_type* create(char* name, const key_compare& comp) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(comp); // Add container to shared pool and return return insert_container(name, c); } // Create new shared container with copy of range // name - name of shared container // f - first iterator // l - last iterator
110
// template<class InputIterator>
static container_type* create(char* name, InputIterator f, InputIterator l) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type<InputIterator>(f, l); // Add container to shared pool and return return insert_container(name, c); }; // Create new shared container with copy of range and key_compare object // name - name of shared container // f - first iterator // l - last iterator // comp - key_compare object // template<class InputIterator>
{ // Allocate new container in shared memory container_type* c = SHARED_NEW container_type<InputIterator>(f, l, comp); // Add container to shared pool and return return insert_container(name, c); }; }; /* * SharedHashContainerFactory - parameterized abstract factory class * - Specialization of the SharedContainerFactory class for hash-based associative containers. * - Implements constructors with copy of range and specified number of buckets, * hash and key_equal functions offered by hash-based associative containers. */ template<class C> class _SharedHashContainerFactory : public _SharedContainerFactory<C> {
111
public: typedef typename container_type::size_type size_type; // Container's size_type typedef typename container_type::key_compare key_compare; // Container's hash function // Redefinition of base class static create methods static container_type* create(char* name)
{ return _SharedContainerFactory<container_type>::create(name); }; static container_type* create(char* name, container_type& c)
{ return _SharedContainerFactory<container_type>::create(name, c); }; // Create new shared container with a number of buckets // name - name of shared container // n - number of buckets // static container_type* create(char* name, size_type n) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(n); // Add container to shared pool and return return insert_container(name, c); } // Create new shared container with a number of buckets and a hash function // name - name of shared container // n - number of buckets // comp - key comparison function // static container_type* create(char* name, size_type n, const key_compare& comp) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type(n, comp); // Add container to shared pool and return return insert_container(name, c); } // Create new shared container with copy of range // name - name of shared container
112
// f - first iterator // l - last iterator // template<class InputIterator> static container_type* create(char* name, InputIterator f, InputIterator l) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type<InputIterator>(f, l); // Add container to shared pool and return return insert_container(name, c); }; // Create new shared container with copy of range and number of buckets // name - name of shared container // f - first iterator // l - last iterator // n - number of buckets // template<class InputIterator>
static container_type* create(char* name, InputIterator f, InputIterator l, size_type n) { // Allocate new container in shared memory container_type* c = SHARED_NEW container_type<InputIterator>(f, l, n); // Add container to shared pool and return return insert_container(name, c); }; // Create new shared container with copy of range, number of buckets // and hash function // name - name of shared container // f - first iterator // l - last iterator // n - number of buckets // comp - key comparison function // template<class InputIterator>
{ // Allocate new container in shared memory container_type* c = SHARED_NEW container_type<InputIterator>(f, l, n, comp); // Add container to shared pool and return return insert_container(name, c); }; };
Listing 13: container_factories.h
B.11 shared_deque.h /* * shared_deque.h - STL containers in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include <deque> #include "container_factories.h" /* * shared_deque class * - derives from the deque and sync classes
114
* - implements the deque class with the shared_allocator template parameter */ template<class T> class shared_deque : public sync, public std::deque<T, shared_allocator<T>> {}; /* * ShareddequeFactory - factory class to create shared deques * - Used to create new shared deques or attach to existing ones. * - Derives from the _SharedSequenceContainerFactory to implement * pre-allocation and replication constructors */ template<class T> class SharedDequeFactory : public _SharedSequenceContainerFactory<shared_deque<T>> {};
Listing 14: shared_deque.h
B.12 shared_stack.h /* * shared_stack.h - STL containers in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */
115
#pragma once #include <stack> #include "shared_deque.h" /* * shared_stack class * - derives from the stack and sync classes * - default sequence class is shared_deque */ template<class T, class Sequence=shared_deque<T>> class shared_stack : public sync, public std::stack<T, Sequence> {}; /* * SharedStackFactory - factory class to create shared stacks * - Used to create new shared stacks or attach to existing ones. * - Derives from the _SharedSequenceContainerFactory to implement * pre-allocation and replication constructors * - Default sequence class is shared_deque */ template<class T, class Sequence=shared_deque<T>> class SharedStackFactory : public _SharedSequenceContainerFactory<shared_stack<T, Sequence>> {};
Listing 15: shared_stack.h
116
B.13 shared_set.h /* * shared_set.h - STL containers in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include <set> #include "container_factories.h" /* * shared_set class * - derives from the set and sync classes * - implements the set class with the shared_allocator template parameter */ template<class Key, class Compare=std::less<Key>> class shared_set : public sync, public std::set<Key, Compare, shared_allocator<Key>> {}; /* * shared_multiset class * - derives from the multiset and sync classes * - implements the multiset class with the shared_allocator template parameter */
117
template<class Key, class Compare=std::less<Key>> class shared_multiset : public sync, public std::multiset<Key, Compare, shared_allocator<Key>> {}; /* * SharedSetFactory - factory class to create shared sets * - Used to create new shared sets or attach to existing ones. * - Derives from the _SharedAssociativeContainerFactory to implement * constructors with copy of range and specified key_compare function * offered by associative containers. */ template<class Key, class Compare=std::less<Key>> class SharedSetFactory : public _SharedAssociativeContainerFactory<shared_set<Key, Compare>> {}; /* * SharedMultisetFactory - factory class to create shared multisets * - Used to create new shared multisets or attach to existing ones. * - Derives from the _SharedAssociativeContainerFactory to implement * constructors with copy of range and specified key_compare function * offered by associative containers. */ template<class Key, class Compare=std::less<Key>> class SharedMultisetFactory : public _SharedAssociativeContainerFactory<shared_multiset<Key, Compare>> {};
Listing 16: shared_set.h
B.14 shared_hash_map.h /* * shared_hash_map.h - STL containers in shared memory * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- *
118
* IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * */ #pragma once #include <hash_map> #include "container_factories.h" /* * shared_hash_map class * - derives from the hash_map and sync classes * - implements the hash_map class with the shared_allocator template parameter */ template<class Key, class Data, class Compare=stdext::hash_compare<Key, std::less<Key>>> class shared_hash_map : public sync, public stdext::hash_map<Key, Data, Compare, shared_allocator<std::pair<const Key, Data>>> {}; /* * shared_hash_multimap class * - derives from the hash_multimap and sync classes * - implements the hash_multimap class with the shared_allocator template parameter */ template<class Key, class Data, class Compare=stdext::hash_compare<Key, std::less<Key>>> class shared_hash_multimap : public sync, public stdext::hash_multimap<Key, Data, Compare, shared_allocator<std::pair<const Key, Data>>> {}; /* * SharedHashMapFactory - factory class to create shared hash maps * - Used to create new shared hash maps or attach to existing ones. * - Derives from the _SharedHashContainerFactory to implement * constructors with copy of range and specified key_compare function * offered by associative containers.
119
*/ template<class Key, class Data, class Compare=stdext::hash_compare<Key, std::less<Key>>>
class SharedHashMapFactory : public _SharedHashContainerFactory<shared_hash_map<Key, Data, Compare>> {}; /* * SharedHashMultimapFactory - factory class to create shared hash multimaps * - Used to create new shared hash multimaps or attach to existing ones. * - Derives from the _SharedHashContainerFactory to implement * constructors with copy of range and specified key_compare function * offered by associative containers. */ template<class Key, class Data, class Compare=stdext::hash_compare<Key, std::less<Key>>>
class SharedHashMultimapFactory : public _SharedHashContainerFactory<shared_hash_multimap<Key, Data, Compare>> {};
Listing 17: shared_hash_map.h
120
Appendix C. PERFORMANCE EVALUATION RESULTS
Table 3 presents the raw performance evaluation results discussed above (averages are in bold).
The latest version of the shared container library test scaffolding is available online at: http://bergeon.francois.perso.neuf.fr/containers/test.zip
D.1 VectorTest.cpp /* * VectorTest.cpp - Test scaffolding vector test * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * * This class implements a vector test * */ #include "VectorTest.h" // Shared container global name static char* _name = "my vector"; // Create/attach vector double VectorTest::create() { // In shared memory if (_shared) { // Instantiate a shared_vector<int> object start_timer(); _s = SharedVectorFactory<int>::create(_name); // Attempt attach if creation failed if (_s == NULL) { start_timer(); _s = SharedVectorFactory<int>::attach(_name); } // Map to a STL vector for testing _c = (std::vector<int>*)_s; } // In local memory else { // Instantiate a vector<int> object start_timer(); _c = new std::vector<int>; } // Error checking
122
if (_c == NULL) return -1.0; return end_timer(); } // Populate vector double VectorTest::populate() { int i; int n = 0; // Start start_timer(); // Exclusive lock if (_shared) _s->write_lock(); // Populate for (i = 0; i < ITER; i++) { // ni = n(i-1) + i n += i; if (_shared) _c->push_back(n); else _c->push_back(n); } // Release exclusive lock if (_shared) _s->write_unlock(); return end_timer(); } // Verify contents double VectorTest::verify() { int i; int n = 0; // Start start_timer(); // Non-exclusive lock if (_shared) _s->read_lock(); // Verify for (i = 0; i < ITER; i++) { // ni = n(i-1) + i n += i; // Fail if no match if ((*_c)[i] != n) { if (_shared) _s->read_unlock(); return -1.0; } } // Release non-exclusive locks if (_shared) _s->read_unlock(); return end_timer(); }
D.2 MapTest.cpp /* * MapTest.cpp - Test scaffolding map test * * --- (c) Francois Bergeon 2008 --- * ---- University of Liverpool ---- * * IMPLEMENTING CONTAINER CLASSES IN SHARED MEMORY * * A dissertation for the completion of a * Master of Science in IT - Software Engineering * * Martha McCormick Dissertation Advisor * * This class implements a vector test * */ #include "MapTest.h" // Shared container global name static char* _name = "my map"; // Create/attach map double MapTest::create() { // In shared memory if (_shared) { // Instantiate a shared_map<string,int> object start_timer(); _s = SharedMapFactory<std::string,int>::create(_name); if (_s == NULL) { start_timer(); _s = SharedMapFactory<std::string,int>::attach(_name); } // Map to a STL map for testing _c = (std::map<std::string,int>*)_s; } else { // Instantiate a map<string,int> object
124
start_timer(); _c = new std::map<std::string,int>; } // Error checking if (_c == NULL) return -1.0; return end_timer(); } // Populate map double MapTest::populate() { int i; char buffer[BUFSIZ]; std::string str; int n = 0; // Start start_timer(); // Exclusive lock if (_shared) _s->write_lock(); // Populate for (i = 0; i < ITER; i++) { // “Char(‘A’ + i%26) & i” char c = 'A' + (i%26); sprintf_s(buffer, BUFSIZ, "%c%d", c, i); str = std::string(buffer); n += i; if (_shared) (*_s)[str] = n; else (*_c)[str] = n; } // Release exclusive lock if (_shared) _s->write_unlock(); return end_timer(); } // Verify contents double MapTest::verify() { int i; char buffer[BUFSIZ]; std::string str; int n1 = 0, n2; // Start start_timer(); // Non-exclusive lock if (_shared) _s->read_lock(); for (i = 0; i < ITER; i++) { // “Char(‘A’ + i%26) & i” char c = 'A' + (i%26); sprintf_s(buffer, BUFSIZ, "%c%d", c, i); str = std::string(buffer); n1 += i;
125
if (_shared) n2 = (*_s)[str]; else n2 = (*_c)[str]; // Fail if no match if (n1 != n2) { if (_shared) _s->read_unlock(); return -1.0; } } // Release non-exclusive locks if (_shared) _s->read_unlock(); return end_timer(); } // Destroy/detach map double MapTest::destroy() { start_timer(); if (_shared) SharedMapFactory<std::string,int>::detach(_name); else delete _c; return end_timer();