閱讀107 返回首頁    go 阿裏雲 go 技術社區[雲棲]


obj-c編程10:Foundation庫中類的使用(6)[線程和操作隊列]

    任何語言都不能避而不談線程這個東東,雖然他是和平台相關的鳥,雖說unix哲學比較討厭線程的說...線程不是萬能靈藥,但有些場合還是需要的.談到線程就不得不考慮同步和死鎖問題,見如下代碼:

#import <Foundation/Foundation.h>

#define msg(...) NSLog(__VA_ARGS__)

@interface Foo:NSObject{
	int i;
	//NSLock *lock;
	NSRecursiveLock *lock;
}

@property(atomic) int i;

@end

@implementation Foo
	@synthesize i;

	-(id)init{
		self = [super init];
		if(self){
			i = 0;
			//lock = [[NSLock alloc] init];
			lock = [[NSRecursiveLock alloc] init];
		}
		return self;
	}

	-(void)loop{
		int org_i = 0;
		for(int x = 0;x < 5000;++x){
			org_i = i;
			[self inc];
			//self.i++;
			msg(@"%@:i has %d to %d",[[NSThread currentThread] name],\
				org_i,i);
		}
	}

	-(void)inc{
		//@synchronized(self){
			[lock lock];
			i++;
			[lock unlock];
		//}
	}

	-(int)val{
		return i;
	}

@end

#define msg(...) NSLog(__VA_ARGS__)

int main(int argc,char *argv[])
{
	@autoreleasepool{
		id t = nil;
		Foo *foo = [[Foo alloc] init];
		NSMutableArray *ary = [[NSMutableArray alloc]init];
		for(int i = 0;i<10;++i){
			t = [[NSThread alloc] initWithTarget:foo selector:@selector(loop) \
				object:nil];
			[t setName:[NSString stringWithFormat:@"thread_%d",i]];
			[ary addObject:t];
		}

		for(NSThread *t in ary){
			[t start];
		}

		BOOL quit = NO;
		while(true){
			for(NSThread *t in ary){
				quit = [t isFinished];
				if(!quit) break;
			}
			if(quit) break;
			[NSThread sleepForTimeInterval:1.0];
		}
		
		msg(@"at last val is %d",[foo val]);
	}
	return 0;
}

以上代碼涉及到線程的創建和同步代碼.除了通過線程實例方法initWithTarget:...方法創建以外還可以通過線程類方法+detachNewThreadSelector:...來創建,後一種方法創建後線程立即執行,而前一種方法需要使用-start方法啟動線程.線程的同步可以通過NSLock類的lock和unlock方法,或者可以使用obj-c的關鍵字:@synchronize來實現快速快速同步語義.對於類屬性來說,可以通過關鍵字atomic來聲明該屬性的原子性,不過實際執行好像不能確保同步,本貓哪裏沒考慮到呢?

    我在使用NSURLConnection類獲取網絡數據的時候發現協議回調方法無法被回調,開始我以為可能回調需要時間執行,所以我在主線程中使用了循環+sleep函數等待其isFinished回調的完成.但是這個循環永遠也不會結束.後來發現NSURLConnection類的工作需要在主線程的"剩餘時間片"中完成,所以你sleep還是不行,等於主線程直接睡眠了不會分到時間片啊.這時你可以使用F庫中提供的NSRunLoop類的功能來完成:

[[NSRunLoop currentRunLoop] runUntilDate ...]

    Cocoa GUI框架不是線程安全的,這意味著其他線程如果需要更新GUI組件必須通過主線程來實現,這可以通過NSObject實例方法-performSelectorOnMainThread:withObject:waitUntilDone:來實現;同樣的除了指定讓主線程做事之外,還可以指定讓其他後台線程上執行選擇器:-performSelector:onThread:waitIntilDone: .

    線程是一種比較底層的OS提供的接口,obj-c提供了包裝線程的更高級的邏輯接口,這就是NSOperation和NSOperationQueue類,這種新型線程模型核心稱為GCD.你隻需把需要完成的任務放入NSOperationQueue隊列中,該隊列會根據係統cpu情況自動生成和銷毀一定數目的線程來完成工作,你就不用操心啦.你可以派生NSOperation類並覆寫其main方法來添加任務單元;你也可以使用F庫自帶的更方便的更專注於任務的NSInvocationOperation和NSBlockOperation類來描述任務單元.最後你可以使用-waitUntilAllOperationsAreFinished方法等待隊列中所有任務都完成.下麵我寫了一個簡單的利用隊列多線程完成任務的代碼,每一項任務單元就是給文件改名(但不改文件夾的名字),所有都在代碼裏嘍:

#import <Foundation/Foundation.h>

#define msg(...) printf("[%d]\n",__LINE__);NSLog(__VA_ARGS__)

@interface RenameConcur:NSObject{
	NSString *path;
	NSString *prefix;
}
	@property NSString *path;
	@property NSString *prefix;

	-(id)initWithPath:(NSString*)path andPrefix:(NSString*)prefix;
	-(void)renameWithPrefix;
@end

@implementation RenameConcur
	@synthesize path,prefix;

	-(id)initWithPath:(NSString*)path_v andPrefix:(NSString*)prefix_v{
		self = [super init];
		if(self){
			path = [path_v stringByExpandingTildeInPath];
			prefix = prefix_v;
		}
		return self;
	}

	-(id)init{
		return [self initWithPath:nil andPrefix:nil];
	}

	-(void)renameWithPrefix{
		if(!prefix || !path){
			msg(@"obj's prefix or path is nil!");
		}
		else{
			NSFileManager *fm = [NSFileManager defaultManager];

			BOOL is_dir = NO;
			if([fm fileExistsAtPath:path isDirectory:&is_dir] == NO){
				msg(@"file %@ is not exist!",path);
				return;
			}else if(is_dir){
				msg(@"path %@ is directory,do nothing!",path);
				return;
			}

			NSString *just_name = [path lastPathComponent];
			NSString *just_path = [path stringByDeletingLastPathComponent];
			NSString *new_path = [NSString stringWithFormat:@"%@/%@%@",just_path,prefix,just_name];
			
			if([fm moveItemAtPath:path toPath:new_path error:NULL] == NO){
				msg(@"rename %@ to %@ failed!",path,new_path);
				return;
			}
		}
	}
@end

int main(int argc,char *argv[])
{
	@autoreleasepool{
		/*
		NSString *path = @"~/src/objc_src/ddd.m";
		NSString *prefix = @"love_";
		RenameConcur *rnc = [[RenameConcur alloc] initWithPath:path andPrefix:prefix]; 
		[rnc renameWithPrefix];

		NSString *dir_path = [@"~/src/dir_love" stringByExpandingTildeInPath];
		NSFileManager *fm = [NSFileManager defaultManager];
		NSArray *files = [fm contentsOfDirectoryAtPath:dir_path error:NULL];
		msg(@"files : %@",files);
		
		for(NSString *file in files){
			[rnc setPath:[NSString stringWithFormat:@"%@/%@",dir_path,file]];
			[rnc renameWithPrefix];
		}
		*/
		NSProcessInfo *process = [NSProcessInfo processInfo];
		NSArray *args = [process arguments];
		if([args count] != 3){
			printf("usage : %s path prefix (notice : path's end don't with /)\n",\
				[[[args objectAtIndex:0] lastPathComponent] \
					cStringUsingEncoding:NSASCIIStringEncoding]);
			return 1;
		}
		NSString *dir_path = [[args objectAtIndex:1] stringByExpandingTildeInPath];
		NSString *prefix = [args objectAtIndex:2];
		NSFileManager *fm = [NSFileManager defaultManager];
		NSArray *files = [fm contentsOfDirectoryAtPath:dir_path error:NULL];
		if(!files){
			msg(@"get dir [%@] files's name failed!",dir_path);
			return 2;
		}

		NSOperationQueue *queue = [[NSOperationQueue alloc] init];
		for(NSString *file in files){
			RenameConcur *rnc_n = [[RenameConcur alloc] initWithPath:\
				[NSString stringWithFormat:@"%@/%@",dir_path,file] andPrefix:prefix];
			NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:rnc_n \
				selector:@selector(renameWithPrefix) object:nil];
			[queue addOperation:op];
		}

		[queue waitUntilAllOperationsAreFinished];
	}
	return 0;
}

最後更新:2017-04-03 05:39:29

  上一篇:go Mysql常用命令行大全
  下一篇:go [ext/iconv/iconv.lo] Error 1