From c6901d78627134706f3c173b8bfd825d4f1c4a28 Mon Sep 17 00:00:00 2001 From: rfm Date: Fri, 1 Oct 2010 18:51:30 +0000 Subject: [PATCH] Add a simple thread pooling utility. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@31464 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 8 +- GNUmakefile | 3 + GSIOThreadPool.h | 92 ++++++++++++++++++ GSIOThreadPool.m | 245 +++++++++++++++++++++++++++++++++++++++++++++++ GSThreadPool.m | 2 +- Performance.h | 1 + 6 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 GSIOThreadPool.h create mode 100644 GSIOThreadPool.m diff --git a/ChangeLog b/ChangeLog index 980606f..425a343 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,10 @@ -2010-10-30 Richard Frith-Macdonald +2010-10-01 Richard Frith-Macdonald + + * GSIOThreadPool.h: + * GSIOThreadPool.m: + Add new methed class for pooling threads to handle I/O and timers. + +2010-09-30 Richard Frith-Macdonald * GSThreadPool.h: * GSThreadPool.m: diff --git a/GNUmakefile b/GNUmakefile index 2d48620..f8715ab 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -36,6 +36,7 @@ Performance_INTERFACE_VERSION=0.3 Performance_OBJC_FILES += \ GSCache.m \ + GSIOThreadPool.m \ GSLinkedList.m \ GSThreadPool.m \ GSThroughput.m \ @@ -46,6 +47,7 @@ Performance_OBJC_FILES += \ Performance_HEADER_FILES += \ GSCache.h \ + GSIOThreadPool.h \ GSLinkedList.h \ GSThreadPool.h \ GSThroughput.h \ @@ -55,6 +57,7 @@ Performance_HEADER_FILES += \ Performance_AGSDOC_FILES += \ GSCache.h \ + GSIOThreadPool.h \ GSLinkedList.h \ GSThreadPool.h \ GSThroughput.h \ diff --git a/GSIOThreadPool.h b/GSIOThreadPool.h new file mode 100644 index 0000000..d335b34 --- /dev/null +++ b/GSIOThreadPool.h @@ -0,0 +1,92 @@ +#if !defined(INCLUDED_GSIOTHREADPOOL) +#define INCLUDED_GSIOTHREADPOOL 1 +/** + Copyright (C) 2010 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: September 2010 + + This file is part of the Performance Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + */ +#import + +@class NSThread; +@class NSLock; + +/** This class provides a thread pool for performing methods which need to + * make use of a runloop for I/O and/or timers.
+ * Operations are performed on these threads using the standard + * -performSelector:onThread:withObject:waitUntilDone: method ... the + * pool is simply used to keep track of allocation of threads so that + * you can share jobs between them. + */ +@interface GSIOThreadPool : NSObject +{ + NSLock *poolLock; + NSMutableArray *threads; + NSTimeInterval timeout; + NSUInteger maxThreads; +} + +/** Returns an instance intended for sharing between sections of code which + * wish to make use of threading by performing operations in other threads, + * but which don't mind operations being interleaved with those belonging to + * oither sections of code.
+ * Always returns the same instance whenever the method is called. + */ ++ (GSIOThreadPool*) sharedPool; + +/** Seects a thread from the pool to be used for some job. + */ +- (NSThread*) acquireThread; + +/** Returns the acquire count for the specified thread. + */ +- (NSUInteger) countForThread: (NSThread*)aThread; + +/** Returns the currently configured maximum number of threads in the pool. + */ +- (NSUInteger) maxThreads; + +/** Specify the maximum number of threads in the pool (the actual number + * used may be lower than this value).
+ * Default is 2.
+ * The pool creates threads on demand up to the specified limit (or a lower + * limit if dictated by system resources) but will not destroy idle threads + * unless the limit is subsequently lowered.
+ * Setting a value of zero means that operations are performed in the + * main thread. + */ +- (void) setThreads: (NSUInteger)max; + +/** Specifies the timeout allowed for a thread to close down when the pool + * is deallocated or has its size decreased. Any operations in progress in + * the thread need to close down within this period. + */ +- (void) setTimeout: (NSTimeInterval)t; + +/** Returns the current timeout set for the pool. + */ +- (NSTimeInterval) timeout; + +/** Releases a thread previously selected from the pool. + */ +- (void) unacquireThread: (NSThread*)aThread; + +@end + +#endif diff --git a/GSIOThreadPool.m b/GSIOThreadPool.m new file mode 100644 index 0000000..b3f9354 --- /dev/null +++ b/GSIOThreadPool.m @@ -0,0 +1,245 @@ +/** + Copyright (C) 2010 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: September 2010 + + This file is part of the Performance Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + */ + +#import +#import +#import +#import +#import +#import +#import "GSIOThreadPool.h" + +@interface GSIOThread : NSThread +{ + NSTimer *timer; + @public + NSUInteger count; +} +- (void) exit: (NSTimer*)t; +- (void) run; +- (void) terminate: (NSDate*)when; +@end + +@implementation GSIOThread + +/* Force termination of this thread. + */ +- (void) exit: (NSTimer*)t +{ + [NSThread exit]; +} + +- (id) init +{ + self = [super initWithTarget: self selector: @selector(run) object: nil]; + if (nil != self) + { + [self start]; + } + return self; +} + +/* Run the thread's main runloop until terminated. + */ +- (void) run +{ + NSDate *when = [NSDate distantFuture]; + NSTimeInterval delay = [when timeIntervalSinceNow]; + + timer = [NSTimer scheduledTimerWithTimeInterval: delay + target: self + selector: @selector(exit:) + userInfo: nil + repeats: NO]; + [[NSRunLoop currentRunLoop] run]; +} + +/* End execution of the thread by the specified date. + */ +- (void) terminate: (NSDate*)when +{ + NSTimeInterval delay = [when timeIntervalSinceNow]; + + [timer invalidate]; + if (delay > 0.0) + { + timer = [NSTimer scheduledTimerWithTimeInterval: delay + target: self + selector: @selector(exit:) + userInfo: nil + repeats: NO]; + } + else + { + timer = nil; + [self exit: nil]; + } +} +@end + + +@implementation GSIOThreadPool + +static GSIOThreadPool *shared = nil; + +/* Return the thread with the lowest usage. + */ +static GSIOThread * +best(NSMutableArray *a) +{ + NSUInteger c = [a count]; + NSUInteger l = NSNotFound; + GSIOThread *t = nil; + + while (c-- > 0) + { + GSIOThread *o = [a objectAtIndex: c]; + + if (o->count < l) + { + t = o; + l = o->count; + } + } + return t; +} + ++ (void) initialize +{ + if ([GSIOThreadPool class] == self && nil == shared) + { + shared = [self new]; + } +} + ++ (GSIOThreadPool*) sharedPool +{ + return shared; +} + +- (NSThread*) acquireThread +{ + GSIOThread *t; + + [poolLock lock]; + t = best(threads); + if (t->count > 0 && [threads count] < maxThreads) + { + t = [GSIOThread new]; + [threads addObject: t]; + [t release]; + } + t->count++; + [poolLock unlock]; + return t; +} + +- (NSUInteger) countForThread: (NSThread*)aThread +{ + NSUInteger count = 0; + + [poolLock lock]; + if ([threads indexOfObjectIdenticalTo: aThread] != NSNotFound) + { + count = ((GSIOThread*)aThread)->count; + } + [poolLock unlock]; + return count; +} + +- (void) dealloc +{ + GSIOThread *thread; + NSDate *when = [NSDate dateWithTimeIntervalSinceNow: timeout]; + + [poolLock lock]; + while ((thread = [threads lastObject]) != nil) + { + [thread performSelector: @selector(terminate:) + onThread: thread + withObject: when + waitUntilDone: NO]; + [threads removeLastObject]; + } + [threads release]; + [poolLock unlock]; + [poolLock release]; + [super dealloc]; +} + +- (id) init +{ + if ((self = [super init]) != nil) + { + poolLock = [NSLock new]; + threads = [NSMutableArray new]; + } + return self; +} + +- (NSUInteger) maxThreads +{ + return maxThreads; +} + +- (void) setThreads: (NSUInteger)max +{ + [poolLock lock]; + if (max != maxThreads) + { + maxThreads = max; + while ([threads count] > maxThreads) + { + } + } + [poolLock unlock]; +} + +- (void) setTimeout: (NSTimeInterval)t +{ + timeout = t; +} + +- (NSTimeInterval) timeout +{ + return timeout; +} + +- (void) unacquireThread: (NSThread*)aThread +{ + [poolLock lock]; + if ([threads indexOfObjectIdenticalTo: aThread] != NSNotFound) + { + if (0 == ((GSIOThread*)aThread)->count) + { + [poolLock unlock]; + [NSException raise: NSInternalInconsistencyException + format: @"-unacquireThread: called too many times"]; + } + ((GSIOThread*)aThread)->count--; + } + [poolLock unlock]; +} + +@end + diff --git a/GSThreadPool.m b/GSThreadPool.m index 19c008e..08a3dfa 100644 --- a/GSThreadPool.m +++ b/GSThreadPool.m @@ -61,7 +61,7 @@ static GSThreadPool *shared = nil; + (void) initialize { - if ([GSThreadPool class] == self) + if ([GSThreadPool class] == self && nil == shared) { shared = [self new]; } diff --git a/Performance.h b/Performance.h index 153dd65..f9709eb 100644 --- a/Performance.h +++ b/Performance.h @@ -25,6 +25,7 @@ */ #import "GSCache.h" +#import "GSIOThreadPool.h" #import "GSLinkedList.h" #import "GSThreadPool.h" #import "GSThroughput.h"