From 7e4ef0cdc9aae8851f3c1f79dc81b7e1d245b84f Mon Sep 17 00:00:00 2001 From: CaS Date: Sun, 20 Jul 2003 06:37:25 +0000 Subject: [PATCH] lockBeforeDate improvements. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@17263 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 9 ++++ Source/NSLock.m | 121 ++++++++++++++++++++++++++---------------- Source/NSThread.m | 130 ++++++++++++++++++++++++++++++++-------------- Testing/thread.m | 49 +++++++++++------ 4 files changed, 208 insertions(+), 101 deletions(-) diff --git a/ChangeLog b/ChangeLog index 72f8ad207..b8e08432a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2003-07-20 Richard Frith-Macdonald + + * Source/NSThread.m: Separate out sleeping into a more efficient + function and improve use of nanosleep + * Source/NSLock.m: Use new private sleeping function from NSThread.m + and rewrite code to be more responsive, especially for cases where + locks are heavily used for rapid interaction between threads. + * Tools/thread.m: Test lockBeforeDate + 2003-07-17 Richard Frith-Macdonald * Source/NSRunLoop.m: ([-acceptInputForMode:beforeDate:]) use the diff --git a/Source/NSLock.m b/Source/NSLock.m index 149059058..e6aef95c6 100644 --- a/Source/NSLock.m +++ b/Source/NSLock.m @@ -1,8 +1,9 @@ /** Mutual exclusion locking classes - Copyright (C) 1996 Free Software Foundation, Inc. + Copyright (C) 1996,2003 Free Software Foundation, Inc. Author: Scott Christley Created: 1996 + Author: Richard Frith-Macdonald This file is part of the GNUstep Objective-C Library. @@ -33,6 +34,63 @@ #include "Foundation/NSLock.h" #include "Foundation/NSException.h" #include "Foundation/NSDebug.h" +#include "Foundation/NSThread.h" + +extern void GSSleepUntilIntervalSinceReferenceDate(NSTimeInterval); +extern NSTimeInterval GSTimeNow(); + +typedef struct { + NSTimeInterval end; + NSTimeInterval i0; + NSTimeInterval i1; + NSTimeInterval max; +} GSSleepInfo; + +static void GSSleepInit(NSDate *limit, GSSleepInfo *context) +{ + context->end = [limit timeIntervalSinceReferenceDate]; + context->i0 = 0.0; + context->i1 = 0.0001; // Initial pause interval. + context->max = 0.25; // Maximum pause interval. +} + +/** + *

Using a pointer to a context structure initialised using GSSleepInit() + * we either pause for a while and return YES or, if the limit date + * has passed, return NO. + *

+ *

The pause intervals start off very small, but rapidly increase + * (following a fibonacci sequence) up to a maximum value. + *

+ *

We use the GSSleepUntilIntervalSinceReferenceDate() function to + * avoid objc runtime messaging overheads and overheads of creating and + * destroying temporary date objects. + *

+ */ +static BOOL GSSleepOrFail(GSSleepInfo *context) +{ + NSTimeInterval when = GSTimeNow(); + NSTimeInterval tmp; + + if (when >= context->end) + { + return NO; + } + tmp = context->i0 + context->i1; + context->i0 = context->i1; + context->i1 = tmp; + if (tmp > context->max) + { + tmp = context->max; + } + when += tmp; + if (when > context->end) + { + when = context->end; + } + GSSleepUntilIntervalSinceReferenceDate(when); + return YES; // Paused. +} // Exceptions @@ -145,32 +203,22 @@ NSString *NSRecursiveLockException = @"NSRecursiveLockException"; * has the lock (but it waits until the time limit is up before returning * NO). */ -- (BOOL) lockBeforeDate: (NSDate *)limit +- (BOOL) lockBeforeDate: (NSDate*)limit { - int x; + int x; + GSSleepInfo ctxt; + + GSSleepInit(limit, &ctxt); /* This is really the behavior of OpenStep, if the current thread has the lock, we just block until the time limit is up. Very odd */ while (_mutex->owner == objc_thread_id() || (x = objc_mutex_trylock(_mutex)) == -1) { - NSDate *current = [NSDate date]; - NSComparisonResult compare; - - compare = [current compare: limit]; - if (compare == NSOrderedSame || compare == NSOrderedDescending) + if (GSSleepOrFail(&ctxt) == NO) { return NO; } -#if defined(__MINGW__) - Sleep(250); // 0.25 second -#else - /* - * This should probably be more accurate like usleep(250) - * but usleep is known to NOT be thread safe under all architectures. - */ - sleep(1); -#endif } return YES; } @@ -378,27 +426,18 @@ NSString *NSRecursiveLockException = @"NSRecursiveLockException"; // Acquiring the lock with a date condition - (BOOL) lockBeforeDate: (NSDate*)limit { + GSSleepInfo ctxt; + CHECK_RECURSIVE_CONDITION_LOCK(_mutex); + GSSleepInit(limit, &ctxt); + while (objc_mutex_trylock(_mutex) == -1) { - NSDate *current = [NSDate date]; - NSComparisonResult compare; - - compare = [current compare: limit]; - if (compare == NSOrderedSame || compare == NSOrderedDescending) + if (GSSleepOrFail(&ctxt) == NO) { return NO; } -#if defined(__MINGW__) - Sleep(250); // 0.25 second -#else - /* - * This should probably be more accurate like usleep(250) - * but usleep is known to NOT be thread safe under all architectures. - */ - sleep(1); -#endif } return YES; } @@ -556,27 +595,17 @@ NSString *NSRecursiveLockException = @"NSRecursiveLockException"; * YES if it can. It returns NO if it cannot * (but it waits until the time limit is up before returning NO). */ -- (BOOL) lockBeforeDate: (NSDate *)limit +- (BOOL) lockBeforeDate: (NSDate*)limit { + GSSleepInfo ctxt; + + GSSleepInit(limit, &ctxt); while (objc_mutex_trylock(_mutex) == -1) { - NSDate *current = [NSDate date]; - NSComparisonResult compare; - - compare = [current compare: limit]; - if (compare == NSOrderedSame || compare == NSOrderedDescending) + if (GSSleepOrFail(&ctxt) == NO) { return NO; } -#if defined(__MINGW__) - Sleep(250); // 0.25 second -#else - /* - * This should probably be more accurate like usleep(250) - * but usleep is known to NOT be thread safe under all architectures. - */ - sleep(1); -#endif } return YES; } diff --git a/Source/NSThread.m b/Source/NSThread.m index 04d61d597..ed705278f 100644 --- a/Source/NSThread.m +++ b/Source/NSThread.m @@ -52,6 +52,94 @@ static Class threadClass = Nil; static NSNotificationCenter *nc = nil; +/** + * Sleep until the current date/time is the specified time interval + * past the reference date/time.
+ * Implemented as a function taking an NSTimeInterval argument in order + * to avoid objc messaging and object allocation/deallocation (NSDate) + * overheads.
+ * Used to implement [NSThread+sleepUntilDate:] + */ +void +GSSleepUntilIntervalSinceReferenceDate(NSTimeInterval when) +{ + extern NSTimeInterval GSTimeNow(); + NSTimeInterval delay; + + // delay is always the number of seconds we still need to wait + delay = when - GSTimeNow(); + +#ifdef HAVE_NANOSLEEP + // Avoid any possibility of overflow by sleeping in chunks. + while (delay > 32768) + { + struct timespec request; + + request.tv_sec = (time_t)32768; + request.tv_nsec = (long)0; + nanosleep(&request, 0); + delay = when - GSTimeNow(); + } + if (delay > 0) + { + struct timespec request; + struct timespec remainder; + + request.tv_sec = (time_t)delay; + request.tv_nsec = (long)((delay - request.tv_sec) * 1000000000); + remainder.tv_sec = 0; + remainder.tv_nsec = 0; + + /* + * With nanosleep, we can restart the sleep after a signal by using + * the remainder information ... so we can be sure to sleep to the + * desired limit without having to re-generate the delay needed. + */ + while (nanosleep(&request, &remainder) < 0 + && (remainder.tv_sec > 0 || remainder.tv_nsec > 0)) + { + request.tv_sec = remainder.tv_sec; + request.tv_nsec = remainder.tv_nsec; + remainder.tv_sec = 0; + remainder.tv_nsec = 0; + } + } +#else + + /* + * Avoid integer overflow by breaking up long sleeps. + */ + while (delay > 30.0*60.0) + { + // sleep 30 minutes +#if defined(__MINGW__) + Sleep (30*60*1000); +#else + sleep (30*60); +#endif + delay = when - GSTimeNow(); + } + + /* + * sleeping may return early because of signals, so we need to re-calculate + * the required delay and check to see if we need to sleep again. + */ + while (delay > 0) + { +#ifdef HAVE_USLEEP + usleep ((int)(delay*1000000)); +#else +#if defined(__MINGW__) + Sleep (delay*1000); +#else + sleep ((int)delay); +#endif +#endif + delay = when - GSTimeNow(); + } +#endif +} + static NSArray * commonModes() { @@ -488,48 +576,10 @@ gnustep_base_thread_callback() */ + (void) sleepUntilDate: (NSDate*)date { - NSTimeInterval delay; - - // delay is always the number of seconds we still need to wait - delay = [date timeIntervalSinceNow]; - - // Avoid integer overflow by breaking up long sleeps - // We assume usleep can accept a value at least 31 bits in length - while (delay > 30.0*60.0) - { - // sleep 30 minutes -#if defined(__MINGW__) - Sleep (30*60*1000); -#else - sleep (30*60); -#endif - delay = [date timeIntervalSinceNow]; - } - - // sleeping may return early because of signals - while (delay > 0) - { -#ifdef HAVE_NANOSLEEP - struct timespec req; - - req.tv_sec = (time_t)delay; - req.tv_nsec = (long)((delay - req.tv_sec) * 1000000000); - nanosleep(&req, 0); -#else -#ifdef HAVE_USLEEP - usleep ((int)(delay*1000000)); -#else -#if defined(__MINGW__) - Sleep (delay*1000); -#else - sleep ((int)delay); -#endif -#endif -#endif - delay = [date timeIntervalSinceNow]; - } + GSSleepUntilIntervalSinceReferenceDate([date timeIntervalSinceReferenceDate]); } + /** * Return the priority of the current thread. */ diff --git a/Testing/thread.m b/Testing/thread.m index 0f259d8e9..b80a52aa6 100644 --- a/Testing/thread.m +++ b/Testing/thread.m @@ -1,5 +1,7 @@ #include +NSLock *lock = nil; + @interface XX : NSObject - (void) fire; - (void) setup; @@ -14,19 +16,27 @@ { CREATE_AUTORELEASE_POOL(arp); - NSLog(@"Setup1"); - [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]]; - NSLog(@"Setup2"); - [self performSelectorOnMainThread: @selector(fire) - withObject: nil - waitUntilDone: NO]; - NSLog(@"Done perform no wait."); - [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]]; - NSLog(@"Setup3"); - [self performSelectorOnMainThread: @selector(fire) - withObject: nil - waitUntilDone: YES]; - NSLog(@"Done perform with wait."); + NSLog(@"Attempting to obtain lock to proceed"); + if ([lock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow: 5.0]] == YES) + { + NSLog(@"Setup1"); + [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]]; + NSLog(@"Setup2"); + [self performSelectorOnMainThread: @selector(fire) + withObject: nil + waitUntilDone: NO]; + NSLog(@"Done perform no wait."); + [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]]; + NSLog(@"Setup3"); + [self performSelectorOnMainThread: @selector(fire) + withObject: nil + waitUntilDone: YES]; + NSLog(@"Done perform with wait."); + } + else + { + NSLog(@"Failed to obtain lock"); + } RELEASE(arp); [NSThread exit]; } @@ -34,17 +44,26 @@ int main(int argc, char **argv, char **env) { - id arp = [NSAutoreleasePool new]; + CREATE_AUTORELEASE_POOL(arp); NSLog(@"Start in main"); + lock = [NSLock new]; + [lock lock]; + [NSThread detachNewThreadSelector: @selector(setup) toTarget: [XX new] withObject: nil]; - + NSLog(@"Waiting to give thread time to start"); + [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]]; + NSLog(@"Releasing lock so thread may proceed"); + [lock unlock]; // Allow other thread to proceed. + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10.0]]; NSLog(@"Done main thread"); + + DESTROY(arp); return 0; }