2013-08-21 31 views
7

Tôi có một số mã nơi tôi đang sử dụng dispatch_semaphore_t để báo hiệu hoạt động hoàn thành. Khi semaphore là một biến thành viên, nó dường như không hoạt động chính xác. Tôi sẽ hiển thị mã ví dụ mà làm việc và một ví dụ mà dường như không làm việc:dispatch_semaphore_t tái sử dụng - Tôi thiếu gì ở đây?

@implementation someClass 
{ 
    dispatch_semaphore_t memberSem; 
    dispatch_semaphore_t* semPtr; 
    NSThread* worker; 
    BOOL taskDone; 
} 

- (id)init 
{ 
    // Set up the worker thread and launch it - not shown here. 
    memberSem= dispatch_semaphore_create(0); 
    semPtr= NULL; 
    taskDone= FALSE; 
} 

- (void)dealloc 
{ 
    // Clean up the worker thread as needed - not shown here. 
    if((NULL != semPtr) && (NULL != *semPtr)) 
    disptatch_release(*semPtr); 

    dispatch_release(memberSem); 
} 

- (void)doSomethingArduous 
{ 
    while([self notDone]) // Does something like check a limit. 
    [self doIt]; // Does something like process data and increment a counter. 

    taskDone= TRUE; // I know this should be protected, but keeping the example simple for now. 

    if((NULL != semPtr) && (NULL != *semPtr)) 
    dispatch_semaphore_signal(*semPtr); // I will put a breakpoint here, call it "SIGNAL" 
} 

- (BOOL)getSomethingDoneUseLocalSemaphore 
{ 
    taskDone= FALSE; // I know this should be protected, but keeping the example simple for now. 
    dispatch_semaphore_t localSem= dispatch_semaphore_create(0); 
    semPtr= &localSem; 
    [self performSelector:doSomethingArduous onThread:worker withObject:nil waitUntilDone:NO]; 

    dispatch_time_t timeUp= dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2.5 * NSEC_PER_SEC)); 
    dispatch_semaphore_wait(localSem, timeUp); 

    semPtr= NULL; 
    dispatch_release(localSem); 

    // I know I could just return taskDone. The example is this way to show what the problem is. 
    if(taskDone) // Again with thread safety. 
    return TRUE;  

    return FALSE; 
} 

- (BOOL)getSomethingDoneUseMemberSemaphore 
{ 
    taskDone= FALSE; // I know this should be protected, but keeping the example simple for now. 

    semPtr= &memberSem; // I will put a breakpoint here, call it "START" 
    [self performSelector:doSomethingArduous onThread:worker withObject:nil waitUntilDone:NO]; 

    dispatch_time_t timeUp= dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2.5 * NSEC_PER_SEC)); 
    dispatch_semaphore_wait(memberSem, timeUp); 

    semPtr= NULL; 

    // I know I could just return taskDone. The example is this way to show what the problem is. 
    if(taskDone) // Again with thread safety. 
    return TRUE; // I will put a breakpoint here, call it "TASK_DONE" 

    return FALSE; // I will put a breakpoint here, call it "TASK_NOT_DONE" 
} 

- (void)hereIsWhereWeBringItTogether 
{ 
    BOOL gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore]; // Will return TRUE. 
    gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore]; // Will return TRUE. 
    gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore]; // Will return TRUE. 

    BOOL gotItDoneMember= [self getSomethingDoneUseMemberSemaphore]; // Will return TRUE. I will put a breakpoint here, call it "RUN_TEST" 
    gotItDoneMember= [self getSomethingDoneUseMemberSemaphore]; // Will return FALSE. 
} 

Vì vậy, cho rằng mã và kết quả tôi nhận được/nhận được, tôi đặt breakpoint như mô tả trong mã thực của tôi: Một trong chức năng chính, một để bắt đầu trong chức năng làm việc, một trong những nơi semaphore thành viên được báo hiệu, và hai sau khi chờ đợi.

Những gì tôi thấy là trong trường hợp tôi sử dụng semaphore thành viên, trong vòng đầu tiên tôi dừng lại tại điểm dừng "RUN_TEST", chạy và nhấn breakpoint "START", chạy rồi nhấn breakpoint "SIGNAL", chạy rồi nhấn breakpoint "TASK_DONE" - tất cả như mong đợi.

Khi tôi tiếp tục chạy, tôi nhấn breakpoint "START", chạy sau đó nhấn breakpoint "TASK_NOT_DONE", chạy sau đó nhấn breakpoint "TÍN HIỆU"

Đó là, khi tôi chạy trình tự sử dụng một semaphore đó là một thành viên, và làm những gì trông giống như tín hiệu thích hợp/chờ đợi, lần thứ hai tôi cố gắng chờ đợi semaphore đó tôi dường như thổi bởi và nó được báo hiệu sau khi tôi đã thoát khỏi chờ đợi.

Tôi có vẻ không quản lý quyền đếm (tín hiệu/cặp đôi chờ đợi) hoặc semaphore thành viên đó sẽ không quay trở lại trạng thái chưa được báo hiệu.

Cảm giác của tôi là có điều gì đó cơ bản mà tôi thiếu ở đây. Mội thông tin đầu vào đều sẽ được xem xét kĩ.

EDIT: Cuối cùng, những gì tôi dường như bị thiếu là do mã thực sự của tôi phức tạp hơn một chút. Thay vì một sự trở lại sạch từ nhiệm vụ khó khăn, có nhiều chủ đề liên quan và một postNotification. Tôi đã thay thế postNotification bằng mã trong trình xử lý thông báo - nó đặt cờ và báo hiệu semaphore. Bằng cách đó, bất kỳ sự chậm trễ nào có thể đã được giới thiệu bởi trình xử lý thông báo đều bị loại bỏ.

Trả lời

7

Vâng, đây là hành vi mong đợi. Nếu bạn hết thời gian chờ tín hiệu, khi tín hiệu đến, nó sẽ bị bắt theo số gọi tiếp theo tới dispatch_semaphore_wait cho semaphore cụ thể đó. Hãy xem xét ví dụ sau:

Ví dụ:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
dispatch_time_t timeout; 

// in 5 seconds, issue signal 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    sleep(5); 
    NSLog(@"Signal 1"); 
    dispatch_semaphore_signal(semaphore); 
}); 

// wait four seconds for signal (i.e. we're going to time out before the signal) 

NSLog(@"Waiting 1"); 
timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)); 
if (dispatch_semaphore_wait(semaphore, timeout)) 
    NSLog(@"Waiting for 1: timed out"); 
else 
    NSLog(@"Waiting for 1: caught signal"); 

// now, let's issue a second signal in another five seconds 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    sleep(5); 
    NSLog(@"Signal 2"); 
    dispatch_semaphore_signal(semaphore); 
}); 

// wait another four seconds for signal 

// this time we're not going to time out waiting for the second signal, 
// because we'll actually catch that first signal, "signal 1") 

NSLog(@"Waiting 2"); 
timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)); 
if (dispatch_semaphore_wait(semaphore, timeout)) 
    NSLog(@"Waiting for 2: timed out"); 
else 
    NSLog(@"Waiting for 2: caught signal"); 

// note, "signal 2" is still forthcoming and the above code's 
// signals and waits are unbalanced 

Vì vậy, khi bạn sử dụng các biến lớp dụ, getSomethingDoneUseMemberSemaphore bạn cư xử như trên, nơi mà các cuộc gọi thứ hai để dispatch_semaphore_wait sẽ bắt tín hiệu đầu tiên ban hành vì (một) đó là cùng một semaphore; và (b) nếu cuộc gọi đầu tiên tới dispatch_semaphore_signal bị hết thời gian chờ.

Nhưng nếu bạn sử dụng các ẩn dụ duy nhất mỗi lần, thì cuộc gọi thứ hai tới dispatch_semaphore_wait sẽ không phản hồi lại số dispatch_semaphore_signal của semaphore đầu tiên.

+0

Tôi hiểu ý của bạn là gì. Về cơ bản, nếu tác vụ mất nhiều thời gian hơn chờ đợi, thời gian chờ sẽ hết thời gian chờ, nhưng tín hiệu sẽ vẫn tăng bộ đếm, vì vậy lần chờ tiếp theo sẽ kích hoạt ngay lập tức. Đó là một cái gì đó tôi nghĩ về, nhưng trong trường hợp này tôi có thể treo lên một máy phân tích và thấy rằng hành động hoàn thành trong thời gian phụ thứ hai. Tôi đoán tôi có thể kiểm tra xem semaphore được báo hiệu lần thứ hai bằng cách chỉ cần chờ đợi * trước * tôi bắt đầu quá trình dài của tôi. Nếu nó không hết thời gian chờ, tôi biết những gì đang diễn ra và có thể điều chỉnh thời gian chờ của tôi một cách thích hợp. – GTAE86

+0

@ GTAE86 Chính xác. BTW, cách bạn đã thiết lập mã semaphore địa phương của mình, bạn có thể, về mặt lý thuyết vẫn thấy hành vi tương tự như ví dụ biến thành viên của bạn (nó hoàn toàn là vấn đề thời gian chính xác). Nếu bạn hoàn toàn không muốn giao tiếp giữa các tín hiệu khác nhau của các nhiệm vụ khác nhau, thay vì chọn lên semaphore từ một số biến mẫu (ngay cả ví dụ "biến cục bộ" hiện tại của bạn sẽ lưu trữ semaphore trong một biến mẫu), bạn muốn để vượt qua semaphore như một tham số, không sử dụng một biến cá thể (hoặc tạo một cá thể riêng biệt của đối tượng của bạn cho mỗi hoạt động). – Rob

+0

Tôi nghĩ rằng tôi * có thể * xem điều gì đang xảy ra trong mã của tôi. Nhiều chủ đề đang được chơi (cách phức tạp hơn ví dụ).Quá trình gian truân là: Trong chủ đề Một chuỗi cuộc gọi B để ghi vào một thiết bị, đợi. Chủ đề C được đáp ứng (ReadPipeAsync), lưu trữ nó trong một deque. Chủ đề D bỏ nó ra để phân tích. Nếu tốt, nó sẽ gửi thông báo với kết quả. Trình xử lý thông báo thực hiện một số kiểm tra, đặt trạng thái hoàn thành (thành một biến khóa được bảo vệ) và báo hiệu semaphore nếu được yêu cầu. Mặc dù tôi biết hoạt động của mình đã hoàn tất nhanh chóng, thông báo đó có thể bị trì hoãn. – GTAE86

1

Tôi đã có thể mã hóa một cái gì đó giống như những gì tôi nghĩ rằng bạn đang tìm kiếm và nó dường như hoạt động theo cách bạn muốn (nhưng một lần nữa, tôi không chắc chắn 100% tôi hiểu những gì bạn đang tìm kiếm cho):.

ArduousTaskDoer.m

@implementation ArduousTaskDoer 
{ 
    dispatch_semaphore_t mSemaphore; 
    BOOL mWorkInProgress; 
} 

- (id)init 
{ 
    if (self = [super init]) 
    { 
     mSemaphore = dispatch_semaphore_create(0); 
    } 
    return self; 
} 

- (void)dealloc 
{ 
    mSemaphore = nil; 
} 

- (void)doWork 
{ 
    @synchronized(self) 
    { 
     mWorkInProgress = YES; 
    } 

    // Do stuff 
    sleep(10); 

    @synchronized(self) 
    { 
     mWorkInProgress = NO; 
    } 

    dispatch_semaphore_signal(mSemaphore); 
} 

- (BOOL)workIsDone 
{ 

    @synchronized(self) 
    { 
     if (!mWorkInProgress) 
     { 
      mWorkInProgress = YES; 
      dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
       [self doWork]; 
      }); 
     } 
    } 


    if (dispatch_semaphore_wait(mSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)2.5 * NSEC_PER_SEC))) 
    { 
     return NO; 
    } 

    return YES; 
} 

@end 

... và sau đó là mã gọi:

ArduousTaskDoer* task = [[ArduousTaskDoer alloc] init]; 
BOOL isDone = NO; 
while(!(isDone = [task workIsDone])) 
{ 
    NSLog(@"Work not done"); 
} 

NSLog(@"Work is done"); 

// Do it again... Semaphore is being reused 
while(!(isDone = [task workIsDone])) 
{ 
    NSLog(@"Work not done"); 
} 

NSLog(@"Work is done"); 

Hope this helps.

+0

@impcc - Cảm ơn bạn đã trả lời. Xin lỗi, nhưng tôi bối rối - trình gỡ lỗi của tôi báo cáo rằng dispatch_time (DISPATCH_TIME_NOW, (int64_t) (2.5 * NSEC_PER_SEC)) trả về cùng giá trị như dispatch_time (DISPATCH_TIME_NOW, (int64_t) 2.5 * NSEC_PER_SEC). Tui bỏ lỡ điều gì vậy? Theo như sự hiểu biết của bạn về những gì tôi đang cố gắng làm - bạn có nó đúng. Tôi chỉ đang cố gắng chờ trong một luồng trên một nhiệm vụ để hoàn thành trong một chủ đề khác. Trong trường hợp của ví dụ của bạn, có vẻ như bạn đang đợi 2,5 giây cho một nhiệm vụ 10 giây để hoàn thành, vì vậy tín hiệu sẽ xảy ra sau khi thời gian chờ đã trôi qua. Chính xác? – GTAE86

+0

Tôi hiểu ... Tôi đã đọc sai mã của bạn và tôi nghĩ bạn vừa chuyển '(2.5 * NSEC_PER_SEC)' trực tiếp đến 'dispatch_semaphore_wait'. Lỗi của tôi. Có, tín hiệu sẽ xảy ra sau lần chờ đầu tiên. Nếu bạn gọi chức năng chờ nhiều hơn một lần, nó sẽ chờ 2,5 giây cho mỗi cuộc gọi. Nếu bạn chỉ muốn đợi cho đến khi tác vụ được thực hiện đầy đủ, chỉ cần sử dụng DISPATCH_TIME_FOREVER. Cách khác, nếu bạn muốn được thông báo không đồng bộ về việc hoàn thành tác vụ, tại sao không sử dụng 'dispatch_group_notify'? – ipmcc

+0

Chủ yếu là vì tôi không thực sự sử dụng GCD, chỉ là các semaphores. Từ những gì tôi đã đọc, tôi có thể phá vỡ tất cả các loại "quy tắc" bằng cách làm điều đó, nhưng nó có vẻ hoạt động rất tốt. Tôi đã đọc tất cả các hướng dẫn của Apple về đồng thời và luồng, và biết họ khuyên bạn nên chống lại chủ đề nếu có thể. Trường hợp của tôi là tôi phải khởi động các phiên I/O mở với nhiều chip - về cơ bản yêu cầu các chip bắt đầu thu thập dữ liệu và xử lý dữ liệu cho đến khi tôi yêu cầu chúng dừng lại. Những người dùng trong thế giới thực có thể chạy nó một cách thực tế trong nhiều giờ tại một thời điểm, do đó, các luồng lệnh/kiểm soát và thu thập dữ liệu có thể chạy trong một thời gian dài. – GTAE86

2

Khi bạn gọi dispatch_semaphore_wait với thời gian chờ và chuỗi vẫn bị chặn tại thời gian chờ, điều gì xảy ra gần như giống như dispatch_semaphore_signal đã được gọi. Một sự khác biệt là dispatch_semaphore_signal sẽ thức dậy bất kỳ chủ đề nào nhưng thời gian chờ sẽ đánh thức chuỗi cụ thể này. Sự khác biệt khác là dispatch_semaphore_wait sẽ trả về giá trị khác 0 thay vì 0.

Đây là vấn đề: Ai sẽ gọi dispatch_semaphore_signal vẫn gọi nó, và sau đó chúng ta có một tín hiệu quá nhiều. Điều này có thể khó tránh; nếu bạn có thời gian chờ 10 giây thì dispatch_semaphore_signal có thể được gọi sau 10.000000001 giây. Vì vậy, nếu bạn đang tái sử dụng semaphore, bạn có một vấn đề ở bàn tay của bạn.

Mặt khác, nếu bạn không sử dụng lại semaphore thì điều tồi tệ nhất xảy ra là số đếm semaphore đi đến 1. Nhưng đó không phải là vấn đề.

Tóm tắt: Không sử dụng lại semaphore nếu bạn đợi nó với thời gian chờ.

+0

Phải. Tôi đã kết thúc việc tạo ra một lớp semaphore-holder, và khi tôi khởi động một hoạt động tôi tạo ra một thể hiện của nó, thiết lập semaphore, và pop nó vào một bộ sưu tập thread-an toàn thuộc sở hữu lớp. Bằng cách đó, mã hoàn thành chạy trong một luồng khác có thể kiểm tra và báo hiệu nó nếu nó tồn tại. Vấn đề chính mà tôi gặp phải trong trường hợp trên là tôi đã báo hiệu semaphore trong một trình xử lý thông báo: Điều đó đã giới thiệu một sự chậm trễ khiến tôi phải chờ thời gian chờ đợi. Vì vậy, có hai điều tôi đã làm dẫn đến vấn đề. Cảm ơn các đầu vào! – GTAE86

Các vấn đề liên quan