먼저 기본적인 GCD의 기능에 대해서 알아보자.


GCD는 태스크를 저장하는 큐(디스패치큐)를 이용하여 동작하며 3종류의 큐가 정의 되어 있다.

첫번째 글에도 잠깐 언급하였지만 간단히 다시 한번 설명하자면,


직렬큐:태스크를 추가한순으로 실행되는 큐

dispatch_queue_create("jp.hateblo.shin.LABEL_NAME", NULL)


병렬큐:추가된태스크를 병렬로 실행하는 큐 (4개의 글로벌 병렬큐가 이미 준비되어있다.)

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


메인큐:추가된태스크가 메인스레드에서 실행되는큐. ( UI작업은 이큐에서 동작해야함.)

큐의 취득은 dispatch_get_main_queue()


각각의 큐는 코드도 서로 다르지만 사용하는방법은 같다.


태스크에 등록할 때에는dispatch_async 나 dispatch_sync의 두가지 방법이 있다. 태스크의 등록할 시점에 동기로 실행할지 비동기일지를 지정한다.


직렬큐라면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 2);

        });

        // 여기서 queue 쌓인 태스크가 전부 끝날때까지 메인스레드는 대기한다.

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 3);

        });

    }

    return 0;

}


실행 결과출력.

2015-03-17 16:05:50.864 TestApp3[5693:1456773] Done: 3

2015-03-17 16:05:50.864 TestApp3[5693:1457020] Done: 1

2015-03-17 16:05:50.864 TestApp3[5693:1457019] Done: 2




병렬큐를 구현한다면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 2);

        });

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 3);

        });

        // 여기서 queue 병렬처리 하고 샆다면..

    }

    return 0;

}


dispatch_sync를 사용함으로써 큐에 쌓인 태스크가 종료될때까지 대기하므로 메인스레드에서 별도의 처리를 할 수 없게된다.

이런경우에는 디스패치 그룹을 사용하면 된다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_group_t group = dispatch_group_create();

        

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 1);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 2);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 3);

        });

        

        // 이지점에서 쌓인큐를 종료됨과 동시에 호출된다.

        // dispatch_group_wait 포함 되지 않는다.

        dispatch_group_notify(group, queue, ^{

            NSLog(@"All tasks are done!");

        });

        

        NSLog(@"do something...");

        [NSThread sleepForTimeInterval:1];

        NSLog(@"waiting...");

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        // 여기서 약간 기다려주지 안흥면 notify처리전에 프로그램의 끝나버리므로..약간 딜레이를

        [NSThread sleepForTimeInterval:1];

    }

    return 0;

}



2015-03-17 16:06:22.744 TestApp3[5717:1461092] do something...

2015-03-17 16:06:23.749 TestApp3[5717:1461092] waiting...

2015-03-17 16:06:25.744 TestApp3[5717:1461228] Done: 3

2015-03-17 16:06:25.744 TestApp3[5717:1461229] Done: 2

2015-03-17 16:06:25.744 TestApp3[5717:1461232] Done: 1

2015-03-17 16:06:25.744 TestApp3[5717:1461232] All tasks are done!





dispatch_group_async를 태스크에 등록을 하면 필요할 경우에 dispatch_group_wait로 큐에 쌓인 태스크가 종료되는것을 기다릴 수 있다.

dispatch_group_notify은 옵션이지만 종료처리에 이용할 수 있다.


디스패치와 세마포어


GCD는 세마포어를 이용할 수도 있다. (세마포어란 전글에도 설명했듯이 배타제어를 위한 방법중 하나이다.)

물론 직렬큐를 잘만 이용한다면 크리티컬섹션을 이용한 세마포어가 필요없겠지만 어쨋든 직력큐와 같은 효과를 낼 수 있다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //열쇠갯수

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:2]; // do something

            NSLog(@"Finish!");

            dispatch_semaphore_signal(semaphore); //열쇠갯수 늘림

        });

        

        [NSThread sleepForTimeInterval:1]; // do something

        NSLog(@"waiting...");

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //열쇠반환될때 까지 기다림

        

    }

    return 0;

}





2015-03-17 16:06:59.677 TestApp3[5746:1465642] waiting...

2015-03-17 16:07:00.677 TestApp3[5746:1465821] Finish!



GCD에 대해선 언급한 것 이외에도 너무나도 많은 기능들이 있으니 애플문서를 참고하면 될듯하다. 











Posted by 악당잰 트랙백 0 : 댓글 4

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 魔術士マジク 2015.03.20 23:47

    검은건 글씨요 흰건 바탕페이지라....ㅠㅠ

    • addr | edit/del 악당잰 2015.03.21 12:56

      에이 ~ 다 알면서~

  2. addr | edit/del | reply 김기봉 2015.03.24 14:46

    병렬큐에서
    dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 2);
    });
    dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 3);
    });

    에서 dispatch_sync랑 dispatch_async를 다르게 준건 무슨 의도로 주신거예요?

  3. addr | edit/del | reply BlogIcon 악당잰 2015.03.24 17:30 신고

    마지막에 싱크를 안해주면 메소드가 끝나버리니까 확인이 안되잖어. 그래서 동기로 3초준거지

저번 스레드글에 이어서 NSOperation에 대해서 조금 덧붙여 이야기 해보고려고 한다. 자세한 내용은 애플 개발자문서에 너무도 자세히 나와 있으므로 여기서는 그냥 이렇게 움직인다라는 설명정도만 하려고 한다.




Cocoa에서는 스레드 병렬처리를 위한 고수준 인터페이스를 제공하고 있다. 뭐냐하면 바로  GCD와NSOperation/NSOperationQueue이 두가지 이다.

이 두가지 방법으로 멀티스레드처리가 가능한데 이 방법의 장점은 직렬큐를 이용하거나(GCD의 경우) 큐의 태스크처리를 지정하는 방법등으로 간단히 골치아픈 데드락을 피할 수 있다. ( 다만 리얼타임처리 요하는 경우에는 스레드를 이용하는편이 낫다. 아주 빠른속도의 처리를 위한 특수한 경우가 해당될듯 하다.)





NSOperation/NSOperationQueue은 내부적으로는 GCD로 구현되어 있다고 한다. 결론적으로 NSOperation/NSOperationQueue는 GCD의Objective-C랩퍼 클래스 인 것이다.

GCD에 비교해서NSOperation/NSOperationQueue가 고수준처리가 가능하다는것은 이전 글에서도 잠깐 언급했지만 얻는만큼 잃는것도 있는법. 그대신 그만큼 오버해드가 발생한다고 한다. 오버해드가 발생할만한 코드를 짜본 적이 없으므로 아직은 잘 모르겠다.

아무튼 그런점의 유념해서 개발시에 GCD를 사용할 것인지를 판단해야 할것이다.


그럼 먼저 NSOperation/NSOperationQueue 에 대해서 설명을 하자면,


NSOperation/NSOperationQueue의 사용방법



NSOperation/NSOperationQueue의 기본적인 개념은 태스크(NSOperation)를 큐(NSOperationQueue)에 추가하여 실행하는 방식으로 설계되어 있다.

코드를 보는편이 이해가 빠를지도..아래의 코드를 보자.


#import <Foundation/Foundation.h>


NSBlockOperation* createAnOperation()

{

    // NSBlockOperation 복수의 블록을 가질 있고 그것을 병렬로 실행 시킬수 있다.

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 1");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 2");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 3");

    }];

    return op;

}


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSArray *ops = @[createAnOperation(), createAnOperation()];

        NSOperationQueue *queue = [[NSOperationQueue allocinit];

        // 여기서 오퍼레이션을 직렬로 실행하기 위해서 오퍼레이션 동시실행수를 1 설정

        [queue setMaxConcurrentOperationCount:1];

        // waitUntilFinishedYES 하면 대기한다.

        [queue addOperations:ops waitUntilFinished:NO];

        // 큐에 직접 블록오퍼레이션을 추가하는 것도 가능하다.

        [queue addOperationWithBlock: ^{

            NSLog(@"All done!");

        }];

        // 전부 오퍼레이션이 끝날때까지 대기.

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


결과는 아래와 같이 출력된다.

2015-03-17 16:04:47.026 TestApp3[5652:1449633] Done: 1

2015-03-17 16:04:47.026 TestApp3[5652:1449632] Done: 2

2015-03-17 16:04:47.026 TestApp3[5652:1449638] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449632] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449638] Done: 1

2015-03-17 16:04:48.032 TestApp3[5652:1449633] Done: 2

2015-03-17 16:04:48.032 TestApp3[5652:1449632] All done!




NSBlockOperation는 표준으로 준비되어 있는 NSOperation의 자식클래스이다. 설명에는 NSBlockOperation만 예를 들었지만 NSInvocationOperation이라는 Target, Selector를 지정해 줄 수 있는 타입도 있다. 또한 NSOperation를 상속받아 커스텀할 수 있도 있다. 더 알고싶다면 애플 개발자 문서를 참조하자.


NSOperationQueue은 기본적으로 GCD라는 병렬큐이다. 위의 예에서는 setMaxConcurrentOperationCount:1로 설정함으로써 직렬큐처럼(태스크가 하나만 처리되므로 결과적으로는 직렬큐) 사용할 수도 있다. 그와 반대로 복수개의 오퍼레이션을  추가하여 이것을 병렬로도 실행 할 수 있다. 


하나 더 유용한 기능이 있는데 NSOperation은 오퍼레이션간에 의존관계를 설정하는 것도 가능하다. 위의 코드는 직렬관계의 오퍼레이션을 동시에 실행하는수를 제한했지만 아래의 코드는 의존관계를 이용하여 실행하는것도 가능하다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSOperation *op1 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 1");

        }];

        NSOperation *op2 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 2");

        }];

        [op2 addDependency: op1]; // op2op1 의존

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];

        [queue addOperations:@[op1, op2] waitUntilFinished:NO];

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


여기서는 설명하지 못했지만 각각의 오퍼레이션의 우선도를 설정한다던지, 완료시에 콜백으로 불려지는등등 블록을 설정하는것도 가능하다. 실제로 이 클래스를 사용해보면 NSThread를 이용하는것보다 훨씬 간편하다는것을 알수 있다.

게다가 멀티스레드에서 제일 골치아픈 배타적제어 문제도 직렬큐를 이용하면 간단히 해결할 수 있으므로 일석이조라고 할 수 있겠다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

스레드는 어떨때 사용할까?


iOS라는 앱에 한정하지 않더라도 어플리케이션이라면 일반적으로 스레드를 사용한다.  보통은 이런경우에 사용하지 않을까 한다.


- 시간이 오래걸리는 작업일 경우

- 동시에 여려작업을 처리하고 싶을경우

- 상태를 계속 감시해야 할 핸들러가 필요할 경우

- 기타....


앱의 경우라면 보통 웹상에 있는 이미지 파일등 다운로드라던가 데이터 파싱등에 사용된다. 이런종류의 처리는 GitHub만 둘러봐도 성능좋은 오픈소스로 많이 공개되어 있기 때문에 굳이 직접 만들 필요까지 없지만.

그러나 아주 가끔 재수없게도 만들어 써야 할 때도 있는데 바로 이번 프로젝트가 그랬다.


이왕 만들어야 되는것이므로 스레드의 개념부터 다시 정리해 보기로 했다. 대학교때 전공으로 배운내용임에도 불구하고 처음 배우는것과 같은 기분이다.




그럼 Objective-C에서 스레드를 어떻게 만들까?

스레드 만드는 방법은 여러가지가 있다. 대표적인것을 꼽아보자면,



NSOperation 

고수준의 편리한 API제공한다.(KVO 키 감시, operation cancel제어등..)

일반 스레드보다 약간 처리속도에 손실이 있다고 한다. 그럼에도 불구하고 편리한 메소드들은 이러한 단점을 커버하기 충분하다.


GCD 

- 블록으로 구현되어 있어 간단하게 사용가능하고 또한 코드 가독성도 좋다.

- 저수준 스레드 구현가능되기에 속도면에서 좋다고 한다. 다만, 상태감시, 오퍼레이션 캔슬 등등 다 직접 만들어야 한다는 귀찮음을 동반한다.


performSelector~:

셀렉터를 이용한 옛날방식이다. GCD를 이용하는것과 거의 같다. 다만 빌드시 ARC 경고가 뜨기도 하는걸로 봐선 ARC와의 궁합이 좋지 않은것 같다. 개인적으로 비추천.


Timer..

간단한 처리는 타이머로 해결 가능하지만 UI이외의 용도로 사용은 글쎄... 좀 불안하다.





스레드를 만들기전에 먼저 알아야할 기초지식



동기처리, 비동기처리

동기란 순차적으로 처리 비동기란 순차적으로 처리되지 않는거다. 예를들어 메소드안에 소스들이 위에서 아래로 순서대로 처리가 되면 동기이고 여기저기 순서없이 처리된다면 비동기이다.


대표적으로 아래의 것들은 비동기처리이다.

딜리게이트(delegate), 셀렉터(@selector), 블록(block), 노티피케이션(Notification)


GCD의 경우는 이런것이다.

dispatch_sync(...), dispatch_async(...)




큐(Queue) 

스레드가 작업을 하기위한 태스크이다. 여러종류가 있는데 우선 직렬큐(serial Queue), 동시큐(Concurrent Queue) 타입으로 나누어진다.


직렬큐는 하나씩 순서대로 처리가 되는 큐를 말한다. (FIFO)

동시큐는 동시에 여러개의 처리가 되는 큐이다.


위에서 설명한 큐는 요렇게 정의 할 수 있다.


//동시큐라면 이렇게..

dispatch_queue_t concurrent_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_CONCURRENT);

    

//직렬큐라면 이렇게 선언

dispatch_queue_t serial_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_SERIAL);




또 구조적으로는 메인큐, 글로벌큐 와 프라이빗큐가 있다.


메인큐(Main dispatch queue)는 UI를 일반적으로 제어하는 큐로써 보통 아무것도 정의하지 않으면 이 큐에서 돌아가게 된다. 

그러므로 무거운처리를 아무처리없이 메소드에 넣으면 화면이 멈추는 현상이 발생하기도 하는데 메인스레드에서 작업이 동작하기 때문이다. 그와 반대로 메인스레드가 아닌 스레드에서 UI작업을 하게되면 UI가 원하는대로 갱신되지 것도 경험했을 것이다.

대표적인 예로 노티피게이션 콜백 메소드등이 후자의 경우이다. 이와같은 경우 UI처리는 보통 메인스레드로 큐를 지정해 주어야 한다. (노티피케이션 콜백 메소드는 메인스레드가 아니므로 UI처리를 할 경우 반드시 GCD를 이용하여 메인스레드로 넘겨줘야 한다.)



글로벌큐는 메인큐와 다른 별도의 큐이다. 글로벌큐 취득 메소드를 이용하여 큐를 얻어오면 되는데 처리우선도라던가 백그라운드로 처리할거라든가를 지정할 수 있어 편리하다. 큐의 종류는 동시큐(Concurrent Queue)이다.

요런식으로 얻어올 수있다.


//처리 우선도를 보통(디폴트)으로 지정하여 취득

dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);



사용자정의 큐(Private Queue)는 개발자가 정의 할 수 있는 큐로서 직렬큐, 동시큐 등으로 지정해 줄수 있다.


//사용자 정의큐의 경우는 identify를 지정한다.

dispatch_queue_t concurrent_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_CONCURRENT);

    


* 덧붙여 설명하자면 NSOperationQueue는 동시큐(Concurrent Queue)이다.




동기처리, 비동기처리 (sync, async)


- 디자인패턴에 의한 비동기처리는 다음과 같은 것들이 있다.

딜리게이트(delegate), 셀렉터(@selector), 블록(block), 노티피케이션(Notification)


- 큐를 이용한 비동기처리 방법은 GCD로 가능하다.

dispatch_sync(...), dispatch_async(...) 

동일한 큐 내에서 동기 비동기를 의미한다. 헷갈리면 안됨. 중요!




데드락(교착상태) 

이상태가 제일 shit!한 상태이다. 어느 스레드도 접근할 수 없는 꼬인 상태인데 보통 화면이 먹통이되어 버린다. (뭐 당연한 얘기지만)


이런 상태는 서로 다른 스레드가 같은 값을 동시에 액서스할때 발생한다.


예를들어 당구장에 당구를 치다가 화장실을 가기위해 당구장 주인에게 열쇠를 받아 화장실에서 볼일을 봤다고 치자. 만약 일을 본 사람이 열쇠를 깜빡 두고 왔다면 어떻게 될까? 열쇠가 없으므로 다음사람은 화장실에 갈수 없게된다. 이런상태를 데드락(교착상태)이라고 한다.


배타적제어

위에서 설명한 데드락을 회피하기 위해 스레드에서도 우선순위를 정하여 순차적으로 접근할 필요가 있다. 어떤 스레드가 어떤 변수에 값을 읽거나 쓰고 있다면 다른 스레드들은 끝날때 까지 대기하고 있다가 처리하는식의 제어를 말한다. 훌륭한 수학자들이 배타적제어에 대한 여러가지 알고리즘을 만들었는데 이해하는건 어려우니 용어만 이라도 알아두자. 


- 크리티컬섹션(임계영역): 데이타의 접근하기 위한 통로?

- 뮤텍스, 세마포어, 인터락...: 배타적 데이타 접근방식


더 자세한 걸 알고 싶다면 위키에서 찾아보도록 하자.





Ojbective-C에서의 배타적제어 방법


위에서 설명한 이론적 베타적 제어를 Ojbective-C에선 어떻게 구현되는지 알아보자.


인터락 함수(Interlocked Family Of Function)(잠금걸쇠가 있는 화장실) 


화장실에 들어간 후 문을 잠근다. 일을 다보고 난 후 문을 열고 나오면 다음사람이 화장실에 들어간다. 이럴경우 한명만 화장실 변기를 사용할 수 있고 중복사용(둘이 같이 화장실에 들어가는?)을 회피할 수 있다.


화장실변기를 변수라고 생각하고 사람을 스레드라고 가정한다면 이게 바로 배타적제어이다.


Objective-C에서는 프로퍼티에 atmoic이라는 지시자를 지정해 줌으로써 간단히 구현이 가능한데 CPU레벨에서의 작업단위(32비트단위인거 같음)에 한해서 1개의 스레드만을 허용하는 방식이다.

그러므로 SInt64이거나 클래스인스턴스 이런건 변수는 하나이지만 CPU처리단위로서는 작업이 나누어져 버리므로 배타제어가 불가능 하다.

이방법으로는 CG돌림자 구조체들(CGPoint,CGRect...)과 location구조체, 스칼라변수(int, float, BOOL)등을 사용할 수 있다.





크리티컬섹션

동기화를 관리하는 영역이다. 화장실에 비유하자면 화장실 열쇠를 가지고 있는 당구장 주인이라 할 수 있다.




크리티컬섹션->뮤텍스방식

열쇠가 하나인 화장실이다. 당구장 주인에게 열쇠를 받아 화장실을 가고 일을본 후에 다시 열쇠를 돌려줘야 한다. 그럼 다음사람이 열쇠를 받아 화장실에 갈수 있고... 방식이라고 생각하면 된다.


Objective-C로 구현한다면 sythroinzed, NSLock, NSRecursiveLock 등으로 구현 할 수 있다.




크리티컬섹션->세마포어방식

열쇠가 여러개 있는 화장실이다. 만약 열쇠가 3개라면 세사람까지 동시에 화장실에서 일을 볼수 있다.

Objective-C에선 아래와 같은 방법으로 선언한다. (보면 알겠지만 GCD를 이용하고 있다.)

dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);






그럼 위에서 설명한 배타적제어를 구현한 소스를 보자.


인터락 함수(Interlocked Family Of Function)(잠금걸쇠가 있는 화장실) 


@property (atomic,readwrite) CGPoint location;


이걸로 끝.




크리티컬섹션->뮤텍스


@synchroized를 이용하여 간단히 구현할 수 있다.


- (void)addItem:(id)object {

    

    // self 키로 락을 . 어디선가 self 락을 건경우 락이 해제될 때까지 여기서 기다리게됨.

    @synchronized (self) {

        [_mutableItems addObject:object];

    }


}


- (void)removeItem:(id)object

{

    //self 키로 락을 . 어디선가 self 락을 건경우 락이 해제될 때까지 여기서 기다리게됨.

    @synchronized (self)

    {

        [_mutableItems removeObject:object];

    }

    

}



이런방법으로 @synchroized에 락을 걸 키를 지정하여 사용할 수도 있다지만 위의 방법으로도 충분할 거 같은 생각이 든다.

@implementation Toliet

{

    

    NSObject* _objectForLock;

}


- (id)init

{

    self = [super init];

    

    if (self)

    {

        _objectForLock = [[NSObject alloc] init];

    }

    

    return self;

}


- (void)addItems:(id)object {

    

    @synchronized (_objectForLock) {

        //이런 방식으로 키를 생성해서 제어 있음.

    }

}


@end




NSLock 을 이용한 방법도 있다.

@interface Toliet () {

    

    NSLock* _lock;

}


@end


@implementation Toliet


@synthesize item = _item;


- (id)init

{

    self = [super init];

    

    if (self)

    {

        _lock = [[NSLock alloc] init];

    }

    

    return self;

}



- (NSObject* )item {

    

    // 락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

    [_lock lock];

    

    @try

    {

        return _item;

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

    }

}


- (void)setItem:(NSObject *)item {

    // 락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

    [_lock lock];

    

    @try

    {

        _item = [item copy];

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

    }

}



데드락을 방지하기 위하여 락을 걸때 아래와 같이 시간을 지정해 줄수도 있다.


- (NSObject* )item {

    

//    //락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

//    [_lock lock];

    

    //만에 하나 처리응답이 없을경우 데드락이 발생되므로 시간을 지정하여 락을 해제할 수도 있다.

    NSDate *timeout  = [NSDate dateWithTimeIntervalSinceNow:30.f];

    [_lock lockBeforeDate:timeout];

    

    @try

    {

        return _item;

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

        

    }

}




또한 NSRecursiveLock 이라는 것도 있다. 사용방법은 위의 NSLock와 같다. 재귀락 개념이라는데 동일 스레드에서 락이 발생했을 경우 락을 걸지 않고 그냥 통과한다고 한다. (해보지 않아서 잘 모르겠음)





세마포어


@interface Toliet2 () {

    

    //세마포어 이용을 위해 먼저 dispatch_semaphore_t 인스턴스를 준비

    dispatch_semaphore_t _semaphore;

}


@end


@implementation Toliet2


@synthesize item = _item;


- (id)init

{

    self = [super init];

    

    if (self)

    {

        //세마포어의 초기화할때 열쇠는 동일스레드, 타스레드 관계없이 제한하므로 주의할것.

        // 화장실 열쇠는 한개라능...

        _semaphore = dispatch_semaphore_create(1);

    }

    

    return self;

}


- (NSObject *)item

{

    NSObject *result;

    

#if 0

    // 열쇠를 가져올때가지 기다림...영원히..

    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

#endif

    

    //그러나 영원히 기다릴수 없는법. 그러면 데드락이 결러버리므로 적정시간이 지나면 락을 풀게 수도 있다.

    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1000*30);

    dispatch_semaphore_wait(_semaphore, timeout);

    

    //열쇠 받고나서 화장실

    result = [_item copy];

    

    // 화장실 갔다왔으니 열쇠반납.

    dispatch_semaphore_signal(_semaphore);

    

    return result;

    

}


- (void)setItem:(NSObject *)item

{

    

    // 열쇠를 가져올때가지 기다림...영원히..

    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

    

    _item = item;


// 열쇠반납.

    dispatch_semaphore_signal(_semaphore);

    

}


@end








에구구 간단할 줄 알았는데 생각보다 꽤 길어져서 아무래도 나누어서 정리해야겠다.

다음번 스레드 이야기는 아래의 내용을 써볼 예정이다.


[iOS]Objective-C로 NSOperation으로 스레드 구현하기(2)

[iOS]Objective-C로 GCD를 이용한 스레드를 구현하기(3)





참고링크

https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ConcurrencyandApplicationDesign/ConcurrencyandApplicationDesign.html#//apple_ref/doc/uid/TP40008091-CH100-SW1







Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply 호링턴 2016.04.15 18:26

    덕분에 스레드 뭐 사용할지 감이 딱오내요