2 * Copyright 2005-2009, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT license.
5 * Copyright 1999, Be Incorporated. All Rights Reserved.
6 * This file may be used under the terms of the Be Sample Code License.
10 #include "MultiLocker.h"
17 #define TIMING MULTI_LOCKER_TIMING
19 # define DEBUG MULTI_LOCKER_DEBUG
23 const int32 LARGE_NUMBER
= 100000;
26 MultiLocker::MultiLocker(const char* baseName
)
41 // build the semaphores
45 snprintf(name
, sizeof(name
), "%s-%s", baseName
, "ReadSem");
46 fReadSem
= create_sem(0, name
);
47 snprintf(name
, sizeof(name
), "%s-%s", baseName
, "WriteSem");
48 fWriteSem
= create_sem(0, name
);
49 snprintf(name
, sizeof(name
), "%s-%s", baseName
, "WriterLock");
50 fWriterLock
= create_sem(0, name
);
52 fReadSem
= create_sem(0, "MultiLocker_ReadSem");
53 fWriteSem
= create_sem(0, "MultiLocker_WriteSem");
54 fWriterLock
= create_sem(0, "MultiLocker_WriterLock");
57 if (fReadSem
>= 0 && fWriteSem
>=0 && fWriterLock
>= 0)
60 fLock
= create_sem(LARGE_NUMBER
, baseName
!= NULL
? baseName
: "MultiLocker");
64 // we are in debug mode!
65 // create the reader tracking list
66 // the array needs to be large enough to hold all possible threads
68 get_system_info(&sys
);
69 fMaxThreads
= sys
.max_threads
;
70 fDebugArray
= (int32
*) malloc(fMaxThreads
* sizeof(int32
));
71 for (int32 i
= 0; i
< fMaxThreads
; i
++) {
76 //initialize the counter variables
77 rl_count
= ru_count
= wl_count
= wu_count
= islock_count
= 0;
78 rl_time
= ru_time
= wl_time
= wu_time
= islock_time
= 0;
80 reg_count
= unreg_count
= 0;
81 reg_time
= unreg_time
= 0;
87 MultiLocker::~MultiLocker()
93 // set locker to be uninitialized
97 // delete the semaphores
99 delete_sem(fWriteSem
);
100 delete_sem(fWriterLock
);
106 // let's produce some performance numbers
107 printf("MultiLocker Statistics:\n"
108 "Avg ReadLock: %lld\n"
109 "Avg ReadUnlock: %lld\n"
110 "Avg WriteLock: %lld\n"
111 "Avg WriteUnlock: %lld\n"
112 "Avg IsWriteLocked: %lld\n",
113 rl_count
> 0 ? rl_time
/ rl_count
: 0,
114 ru_count
> 0 ? ru_time
/ ru_count
: 0,
115 wl_count
> 0 ? wl_time
/ wl_count
: 0,
116 wu_count
> 0 ? wu_time
/ wu_count
: 0,
117 islock_count
> 0 ? islock_time
/ islock_count
: 0);
123 MultiLocker::InitCheck()
130 This function demonstrates a nice method of determining if the current thread
131 is the writer or not. The method involves caching the index of the page in memory
132 where the thread's stack is located. Each time a new writer acquires the lock,
133 its thread_id and stack_page are recorded. IsWriteLocked gets the stack_page of the
134 current thread and sees if it is a match. If the stack_page matches you are guaranteed
135 to have the matching thread. If the stack page doesn't match the more traditional
136 find_thread(NULL) method of matching the thread_ids is used.
138 This technique is very useful when dealing with a lock that is acquired in a nested fashion.
139 It could be expanded to cache the information of the last thread in the lock, and then if
140 the same thread returns while there is no one in the lock, it could save some time, if the
141 same thread is likely to acquire the lock again and again.
142 I should note another shortcut that could be implemented here
143 If fWriterThread is set to -1 then there is no writer in the lock, and we could
144 return from this function much faster. However the function is currently set up
145 so all of the stack_base and thread_id info is determined here. WriteLock passes
146 in some variables so that if the lock is not held it does not have to get the thread_id
147 and stack base again. Instead this function returns that information. So this shortcut
148 would only move this information gathering outside of this function, and I like it all
152 MultiLocker::IsWriteLocked(addr_t
* _stackBase
, thread_id
* _thread
) const
155 bigtime_t start
= system_time();
158 // get a variable on the stack
159 bool writeLockHolder
= false;
162 // determine which page in memory this stack represents
163 // this is managed by taking the address of the item on the
164 // stack and dividing it by the size of the memory pages
165 // if it is the same as the cached stack_page, there is a match
166 addr_t stackBase
= (addr_t
)&writeLockHolder
/ B_PAGE_SIZE
;
167 thread_id thread
= 0;
169 if (fWriterStackBase
== stackBase
) {
170 writeLockHolder
= true;
172 // as there was no stack page match we resort to the
173 // tried and true methods
174 thread
= find_thread(NULL
);
175 if (fWriterThread
== thread
)
176 writeLockHolder
= true;
179 // if someone wants this information, give it to them
180 if (_stackBase
!= NULL
)
181 *_stackBase
= stackBase
;
187 bigtime_t end
= system_time();
188 islock_time
+= (end
- start
);
192 return writeLockHolder
;
197 // #pragma mark - Standard versions
201 MultiLocker::ReadLock()
204 bigtime_t start
= system_time();
209 // the lock must be initialized
211 if (IsWriteLocked()) {
212 // the writer simply increments the nesting
216 // increment and retrieve the current count of readers
217 int32 currentCount
= atomic_add(&fReadCount
, 1);
218 if (currentCount
< 0) {
219 // a writer holds the lock so wait for fReadSem to be released
222 status
= acquire_sem(fReadSem
);
223 } while (status
== B_INTERRUPTED
);
225 locked
= status
== B_OK
;
232 bigtime_t end
= system_time();
233 rl_time
+= (end
- start
);
242 MultiLocker::WriteLock()
245 bigtime_t start
= system_time();
251 addr_t stackBase
= 0;
252 thread_id thread
= -1;
254 if (IsWriteLocked(&stackBase
, &thread
)) {
255 // already the writer - increment the nesting count
259 // new writer acquiring the lock
260 if (atomic_add(&fLockCount
, 1) >= 1) {
261 // another writer in the lock - acquire the semaphore
264 status
= acquire_sem(fWriterLock
);
265 } while (status
== B_INTERRUPTED
);
267 locked
= status
== B_OK
;
272 // new holder of the lock
274 // decrement fReadCount by a very large number
275 // this will cause new readers to block on fReadSem
276 int32 readers
= atomic_add(&fReadCount
, -LARGE_NUMBER
);
278 // readers hold the lock - acquire fWriteSem
281 status
= acquire_sem_etc(fWriteSem
, readers
, 0, 0);
282 } while (status
== B_INTERRUPTED
);
284 locked
= status
== B_OK
;
287 ASSERT(fWriterThread
== -1);
288 // record thread information
289 fWriterThread
= thread
;
290 fWriterStackBase
= stackBase
;
297 bigtime_t end
= system_time();
298 wl_time
+= (end
- start
);
307 MultiLocker::ReadUnlock()
310 bigtime_t start
= system_time();
313 bool unlocked
= false;
315 if (IsWriteLocked()) {
316 // writers simply decrement the nesting count
320 // decrement and retrieve the read counter
321 int32 current_count
= atomic_add(&fReadCount
, -1);
322 if (current_count
< 0) {
323 // a writer is waiting for the lock so release fWriteSem
324 unlocked
= release_sem_etc(fWriteSem
, 1, B_DO_NOT_RESCHEDULE
) == B_OK
;
330 bigtime_t end
= system_time();
331 ru_time
+= (end
- start
);
340 MultiLocker::WriteUnlock()
343 bigtime_t start
= system_time();
346 bool unlocked
= false;
348 if (IsWriteLocked()) {
349 // if this is a nested lock simply decrement the nest count
350 if (fWriterNest
> 0) {
354 // writer finally unlocking
356 // increment fReadCount by a large number
357 // this will let new readers acquire the read lock
358 // retrieve the number of current waiters
359 int32 readersWaiting
= atomic_add(&fReadCount
, LARGE_NUMBER
)
362 if (readersWaiting
> 0) {
363 // readers are waiting to acquire the lock
364 unlocked
= release_sem_etc(fReadSem
, readersWaiting
,
365 B_DO_NOT_RESCHEDULE
) == B_OK
;
370 // clear the information
372 fWriterStackBase
= 0;
374 // decrement and retrieve the lock count
375 if (atomic_add(&fLockCount
, -1) > 1) {
376 // other writers are waiting so release fWriterLock
377 unlocked
= release_sem_etc(fWriterLock
, 1,
378 B_DO_NOT_RESCHEDULE
) == B_OK
;
383 debugger("Non-writer attempting to WriteUnlock()");
386 bigtime_t end
= system_time();
387 wu_time
+= (end
- start
);
396 // #pragma mark - Debug versions
400 MultiLocker::ReadLock()
405 debugger("lock not initialized");
407 if (IsWriteLocked()) {
409 debugger("ReadLock() - negative writer nest count");
416 status
= acquire_sem(fLock
);
417 } while (status
== B_INTERRUPTED
);
419 locked
= status
== B_OK
;
430 MultiLocker::WriteLock()
435 debugger("lock not initialized");
437 addr_t stackBase
= 0;
438 thread_id thread
= -1;
440 if (IsWriteLocked(&stackBase
, &thread
)) {
442 debugger("WriteLock() - negative writer nest count");
447 // new writer acquiring the lock
449 debugger("Reader wants to become writer!");
453 status
= acquire_sem_etc(fLock
, LARGE_NUMBER
, 0, 0);
454 } while (status
== B_INTERRUPTED
);
456 locked
= status
== B_OK
;
458 // record thread information
459 fWriterThread
= thread
;
460 fWriterStackBase
= stackBase
;
469 MultiLocker::ReadUnlock()
471 bool unlocked
= false;
473 if (IsWriteLocked()) {
474 // writers simply decrement the nesting count
477 debugger("ReadUnlock() - negative writer nest count");
481 // decrement and retrieve the read counter
482 unlocked
= release_sem_etc(fLock
, 1, B_DO_NOT_RESCHEDULE
) == B_OK
;
492 MultiLocker::WriteUnlock()
494 bool unlocked
= false;
496 if (IsWriteLocked()) {
497 // if this is a nested lock simply decrement the nest count
498 if (fWriterNest
> 0) {
502 // clear the information while still holding the lock
504 fWriterStackBase
= 0;
505 unlocked
= release_sem_etc(fLock
, LARGE_NUMBER
,
506 B_DO_NOT_RESCHEDULE
) == B_OK
;
510 snprintf(message
, sizeof(message
), "Non-writer attempting to "
511 "WriteUnlock() - write holder: %" B_PRId32
, fWriterThread
);
520 MultiLocker::IsReadLocked() const
522 if (fInit
== B_NO_INIT
)
525 // determine if the lock is actually held
526 thread_id thread
= find_thread(NULL
);
527 return fDebugArray
[thread
% fMaxThreads
] > 0;
531 /* these two functions manage the debug array for readers */
532 /* an array is created in the constructor large enough to hold */
533 /* an int32 for each of the maximum number of threads the system */
534 /* can have at one time. */
535 /* this array does not need to be locked because each running thread */
536 /* can be uniquely mapped to a slot in the array by performing: */
537 /* thread_id % max_threads */
538 /* each time ReadLock is called while in debug mode the thread_id */
539 /* is retrived in register_thread() and the count is adjusted in the */
540 /* array. If register thread is ever called and the count is not 0 then */
541 /* an illegal, potentially deadlocking nested ReadLock occured */
542 /* unregister_thread clears the appropriate slot in the array */
544 /* this system could be expanded or retracted to include multiple arrays of information */
545 /* in all fairness for it's current use, fDebugArray could be an array of bools */
547 /* The disadvantage of this system for maintaining state is that it sucks up a ton of */
548 /* memory. The other method (which would be slower), would involve an additional lock and */
549 /* traversing a list of cached information. As this is only for a debug mode, the extra memory */
550 /* was not deemed to be a problem */
553 MultiLocker::_RegisterThread()
555 thread_id thread
= find_thread(NULL
);
557 if (fDebugArray
[thread
% fMaxThreads
] != 0)
558 debugger("Nested ReadLock!");
560 fDebugArray
[thread
% fMaxThreads
]++;
565 MultiLocker::_UnregisterThread()
567 thread_id thread
= find_thread(NULL
);
569 ASSERT(fDebugArray
[thread
% fMaxThreads
] == 1);
570 fDebugArray
[thread
% fMaxThreads
]--;