はじめに
Java言語のマルチスレッドデザインパターンのObjective-C言語版がないかと探してみましたが、見つからなったので自分で作成してみました。
はじめに Single Threaded Execution パターンを実装してみます。
非同期の実装方法は、Appleの公式ドキュメントで GCD(Grand Central Dispatch) の使用を推奨されているのでそれを利用。
環境
- MacOS High Sierra 10.13.6
- Xcode 9.4.1
スレッドセーフではないサンプルプログラム
まずはスレッドセーフではないGateクラスとUserThreadクラスの実装です。
Gate.h
@interface Gate : NSObject
- (void) pass:(NSString*)name address:(NSString*)address;
- (NSString*) toString;
@end
Gate.m
@interface Gate () {
NSInteger _counter;
NSString *_name;
NSString *_address;
}
@end
@implementation Gate
- (id) init {
if(self = [super init]) {
_counter = 0;
_name = @"Nobody";
_address = @"nowhere";
}
return self;
}
- (void) pass:(NSString*)name address:(NSString*)address {
_counter++;
_name = name;
_address = address;
[self check];
}
- (NSString*) toString {
return [NSString stringWithFormat:@"No. %@ %@, %@", @(_counter), _name, _address];
}
- (void) check {
if(![[_name substringToIndex:1] isEqualToString:[_address substringToIndex:1]]) {
NSLog(@"**** BROKEN **** %@", [self toString]);
}
}
@end
UserThread.h
@interface UserThread : NSObject
- (id) initWithGate:(Gate*)gate name:(NSString*)myName address:(NSString*)myAddress;
- (void) start;
@end
UserThread.m
@interface UserThread () {
Gate *_gate;
NSString *_myName;
NSString *_myAddress;
}
@end
@implementation UserThread
- (id) initWithGate:(Gate*)gate name:(NSString*)myName address:(NSString*)myAddress {
if(self = [super init]) {
_gate = gate;
_myName = myName;
_myAddress = myAddress;
}
return self;
}
- (void) start {
dispatch_queue_t queue = dispatch_queue_create("co.jp.SingleThreadedExecution.Queue", NULL);
dispatch_async(queue, ^{
[self run];
});
}
- (void) run {
NSLog(@"%@ BEGIN",_myName);
while (true) {
[_gate pass:_myName address:_myAddress];
}
}
@end
ViewController.m
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self init];
}
- (void)init {
Gate *gate = [[Gate alloc] init];
[[[UserThread alloc] initWithGate:gate name:@"Alice" address:@"Alaska"] start];
[[[UserThread alloc] initWithGate:gate name:@"Bobby" address:@"Brazil"] start];
[[[UserThread alloc] initWithGate:gate name:@"Chris" address:@"Canada"] start];
}
@end
実行結果
実行してみると、ログにBROKENが出力されます。
Alice BEGIN
Bobby BEGIN
Chris BEGIN
**** BROKEN **** No. 4947 Alice, Alaska
**** BROKEN **** No. 5569 Alice, Alaska
**** BROKEN **** No. 6149 Bobby, Brazil
**** BROKEN **** No. 13204 Chris, Canada
スレッドセーフに修正
Gateクラスを @synchronized を使用してスレッドセーフに修正。
Gate.m
- (void) pass:(NSString*)name address:(NSString*)address {
@synchronized(self) {
_counter++;
_name = name;
_address = address;
[self check];
}
}
- (NSString*) toString {
NSString *ret;
@synchronized(self) {
ret = [NSString stringWithFormat:@"No. %@ %@, %@", @(_counter), _name, _address];
}
return ret;
}
実行結果
実行してみると、ログにBROKENが出力されなくなりました。
Alice BEGIN
Bobby BEGIN
Chris BEGIN
サンプルソース
その他
今回 @synchronized を使用しましたが、「スレッドプログラミングガイド」にあるとおりNSLockクラスやNSRecursiveLockクラスを使用してみるのもいいと思います。