3 Copyright 1999, Be Incorporated. All Rights Reserved.
4 This file may be used under the terms of the Be Sample Code License.
7 #include "MultiLocker.h"
17 MultiLocker::MultiLocker(const char* semaphoreBaseName
)
31 //build the semaphores
32 if (semaphoreBaseName
) {
34 sprintf(name
, "%s-%s", semaphoreBaseName
, "ReadSem");
35 fReadSem
= create_sem(0, name
);
36 sprintf(name
, "%s-%s", semaphoreBaseName
, "WriteSem");
37 fWriteSem
= create_sem(0, name
);
38 sprintf(name
, "%s-%s", semaphoreBaseName
, "WriterLock");
39 fWriterLock
= create_sem(0, name
);
41 fReadSem
= create_sem(0, "MultiLocker_ReadSem");
42 fWriteSem
= create_sem(0, "MultiLocker_WriteSem");
43 fWriterLock
= create_sem(0, "MultiLocker_WriterLock");
46 if (fReadSem
>= 0 && fWriteSem
>=0 && fWriterLock
>= 0)
50 //we are in debug mode!
51 //create the reader tracking list
52 //the array needs to be large enough to hold all possible threads
54 get_system_info(&sys
);
55 fMaxThreads
= sys
.max_threads
;
56 fDebugArray
= (int32
*) malloc(fMaxThreads
* sizeof(int32
));
57 for (int32 i
= 0; i
< fMaxThreads
; i
++) {
63 //initialize the counter variables
64 rl_count
= ru_count
= wl_count
= wu_count
= islock_count
= 0;
65 rl_time
= ru_time
= wl_time
= wu_time
= islock_time
= 0;
67 reg_count
= unreg_count
= 0;
68 reg_time
= unreg_time
= 0;
74 MultiLocker::~MultiLocker()
77 if (!IsWriteLocked()) WriteLock();
79 //set locker to be uninitialized
82 //delete the semaphores
84 delete_sem(fWriteSem
);
85 delete_sem(fWriterLock
);
88 //we are in debug mode!
89 //clear and delete the reader tracking list
93 //let's produce some performance numbers
94 printf("MultiLocker Statistics:\n"
95 "Avg ReadLock: %lld\n"
96 "Avg ReadUnlock: %lld\n"
97 "Avg WriteLock: %lld\n"
98 "Avg WriteUnlock: %lld\n"
99 "Avg IsWriteLocked: %lld\n",
100 rl_count
> 0 ? rl_time
/ rl_count
: 0,
101 ru_count
> 0 ? ru_time
/ ru_count
: 0,
102 wl_count
> 0 ? wl_time
/ wl_count
: 0,
103 wu_count
> 0 ? wu_time
/ wu_count
: 0,
104 islock_count
> 0 ? islock_time
/ islock_count
: 0
107 printf( "Avg register_thread: %lld\n"
108 "Avg unregister_thread: %lld\n",
109 reg_count
> 0 ? reg_time
/ reg_count
: 0,
110 unreg_count
> 0 ? unreg_time
/ unreg_count
: 0
117 MultiLocker::InitCheck()
123 MultiLocker::ReadLock()
126 bigtime_t start
= system_time();
131 //the lock must be initialized
133 if (IsWriteLocked()) {
134 //the writer simply increments the nesting
138 //increment and retrieve the current count of readers
139 int32 current_count
= atomic_add(&fReadCount
, 1);
140 if (current_count
< 0) {
141 //a writer holds the lock so wait for fReadSem to be released
142 locked
= (acquire_sem_etc(fReadSem
, 1, B_DO_NOT_RESCHEDULE
,
143 B_INFINITE_TIMEOUT
) == B_OK
);
144 } else locked
= true;
146 //register if we acquired the lock
147 if (locked
) register_thread();
154 bigtime_t end
= system_time();
155 rl_time
+= (end
- start
);
163 MultiLocker::WriteLock()
166 bigtime_t start
= system_time();
172 uint32 stack_base
= 0;
173 thread_id thread
= -1;
175 if (IsWriteLocked(&stack_base
, &thread
)) {
176 //already the writer - increment the nesting count
180 //new writer acquiring the lock
181 if (atomic_add(&fLockCount
, 1) >= 1) {
182 //another writer in the lock - acquire the semaphore
183 locked
= (acquire_sem_etc(fWriterLock
, 1, B_DO_NOT_RESCHEDULE
,
184 B_INFINITE_TIMEOUT
) == B_OK
);
185 } else locked
= true;
188 //new holder of the lock
190 //decrement fReadCount by a very large number
191 //this will cause new readers to block on fReadSem
192 int32 readers
= atomic_add(&fReadCount
, -LARGE_NUMBER
);
195 //readers hold the lock - acquire fWriteSem
196 locked
= (acquire_sem_etc(fWriteSem
, readers
, B_DO_NOT_RESCHEDULE
,
197 B_INFINITE_TIMEOUT
) == B_OK
);
200 ASSERT(fWriterThread
== -1);
201 //record thread information
202 fWriterThread
= thread
;
203 fWriterStackBase
= stack_base
;
210 bigtime_t end
= system_time();
211 wl_time
+= (end
- start
);
219 MultiLocker::ReadUnlock()
222 bigtime_t start
= system_time();
225 bool unlocked
= false;
227 if (IsWriteLocked()) {
228 //writers simply decrement the nesting count
232 //decrement and retrieve the read counter
233 int32 current_count
= atomic_add(&fReadCount
, -1);
234 if (current_count
< 0) {
235 //a writer is waiting for the lock so release fWriteSem
236 unlocked
= (release_sem_etc(fWriteSem
, 1,
237 B_DO_NOT_RESCHEDULE
) == B_OK
);
238 } else unlocked
= true;
241 //unregister if we released the lock
242 if (unlocked
) unregister_thread();
247 bigtime_t end
= system_time();
248 ru_time
+= (end
- start
);
256 MultiLocker::WriteUnlock()
259 bigtime_t start
= system_time();
262 bool unlocked
= false;
264 if (IsWriteLocked()) {
265 //if this is a nested lock simply decrement the nest count
266 if (fWriterNest
> 0) {
270 //writer finally unlocking
272 //increment fReadCount by a large number
273 //this will let new readers acquire the read lock
274 //retrieve the number of current waiters
275 int32 readersWaiting
= atomic_add(&fReadCount
, LARGE_NUMBER
) + LARGE_NUMBER
;
277 if (readersWaiting
> 0) {
278 //readers are waiting to acquire the lock
279 unlocked
= (release_sem_etc(fReadSem
, readersWaiting
,
280 B_DO_NOT_RESCHEDULE
) == B_OK
);
281 } else unlocked
= true;
284 //clear the information
286 fWriterStackBase
= 0;
288 //decrement and retrieve the lock count
289 if (atomic_add(&fLockCount
, -1) > 1) {
290 //other writers are waiting so release fWriterLock
291 unlocked
= (release_sem_etc(fWriterLock
, 1,
292 B_DO_NOT_RESCHEDULE
) == B_OK
);
297 } else debugger("Non-writer attempting to WriteUnlock()\n");
300 bigtime_t end
= system_time();
301 wu_time
+= (end
- start
);
308 /* this function demonstrates a nice method of determining if the current thread */
309 /* is the writer or not. The method involves caching the index of the page in memory */
310 /* where the thread's stack is located. Each time a new writer acquires the lock, */
311 /* its thread_id and stack_page are recorded. IsWriteLocked gets the stack_page of the */
312 /* current thread and sees if it is a match. If the stack_page matches you are guaranteed */
313 /* to have the matching thread. If the stack page doesn't match the more traditional */
314 /* find_thread(NULL) method of matching the thread_ids is used. */
316 /* This technique is very useful when dealing with a lock that is acquired in a nested fashion. */
317 /* It could be expanded to cache the information of the last thread in the lock, and then if */
318 /* the same thread returns while there is no one in the lock, it could save some time, if the */
319 /* same thread is likely to acquire the lock again and again. */
320 /* I should note another shortcut that could be implemented here */
321 /* If fWriterThread is set to -1 then there is no writer in the lock, and we could */
322 /* return from this function much faster. However the function is currently set up */
323 /* so all of the stack_base and thread_id info is determined here. WriteLock passes */
324 /* in some variables so that if the lock is not held it does not have to get the thread_id */
325 /* and stack base again. Instead this function returns that information. So this shortcut */
326 /* would only move this information gathering outside of this function, and I like it all */
330 MultiLocker::IsWriteLocked(uint32
*the_stack_base
, thread_id
*the_thread
)
333 bigtime_t start
= system_time();
336 //get a variable on the stack
337 bool write_lock_holder
= false;
341 thread_id thread
= 0;
343 //determine which page in memory this stack represents
344 //this is managed by taking the address of the item on the
345 //stack and dividing it by the size of the memory pages
346 //if it is the same as the cached stack_page, there is a match
347 stack_base
= (uint32
) &write_lock_holder
/B_PAGE_SIZE
;
348 if (fWriterStackBase
== stack_base
) {
349 write_lock_holder
= true;
351 //as there was no stack_page match we resort to the
352 //tried and true methods
353 thread
= find_thread(NULL
);
354 if (fWriterThread
== thread
) {
355 write_lock_holder
= true;
359 //if someone wants this information, give it to them
360 if (the_stack_base
!= NULL
) {
361 *the_stack_base
= stack_base
;
363 if (the_thread
!= NULL
) {
364 *the_thread
= thread
;
369 bigtime_t end
= system_time();
370 islock_time
+= (end
- start
);
374 return write_lock_holder
;
378 MultiLocker::IsReadLocked()
380 //a properly initialized MultiLocker in non-debug always returns true
382 if (fInit
== B_NO_INIT
) locked
= false;
385 //determine if the lock is actually held
386 thread_id thread
= find_thread(NULL
);
387 if (fDebugArray
[thread
% fMaxThreads
] > 0) locked
= true;
395 /* these two functions manage the debug array for readers */
396 /* an array is created in the constructor large enough to hold */
397 /* an int32 for each of the maximum number of threads the system */
398 /* can have at one time. */
399 /* this array does not need to be locked because each running thread */
400 /* can be uniquely mapped to a slot in the array by performing: */
401 /* thread_id % max_threads */
402 /* each time ReadLock is called while in debug mode the thread_id */
403 /* is retrived in register_thread() and the count is adjusted in the */
404 /* array. If register thread is ever called and the count is not 0 then */
405 /* an illegal, potentially deadlocking nested ReadLock occured */
406 /* unregister_thread clears the appropriate slot in the array */
408 /* this system could be expanded or retracted to include multiple arrays of information */
409 /* in all fairness for it's current use, fDebugArray could be an array of bools */
411 /* The disadvantage of this system for maintaining state is that it sucks up a ton of */
412 /* memory. The other method (which would be slower), would involve an additional lock and */
413 /* traversing a list of cached information. As this is only for a debug mode, the extra memory */
414 /* was not deemed to be a problem */
417 MultiLocker::register_thread()
421 bigtime_t start
= system_time();
424 thread_id thread
= find_thread(NULL
);
426 ASSERT_WITH_MESSAGE(fDebugArray
[thread
%fMaxThreads
] == 0,"Nested ReadLock!\n");
427 fDebugArray
[thread
%fMaxThreads
]++;
430 bigtime_t end
= system_time();
431 reg_time
+= (end
- start
);
435 debugger("register_thread should never be called unless in DEBUG mode!\n");
440 MultiLocker::unregister_thread()
444 bigtime_t start
= system_time();
447 thread_id thread
= find_thread(NULL
);
449 ASSERT(fDebugArray
[thread
%fMaxThreads
] == 1);
450 fDebugArray
[thread
%fMaxThreads
]--;
453 bigtime_t end
= system_time();
454 unreg_time
+= (end
- start
);
458 debugger("unregister_thread should never be called unless in DEBUG mode!\n");