2009-02-16 24 views
15

My iPhone khách hàng có rất nhiều sự tham gia của các yêu cầu không đồng bộ, rất nhiều thời gian liên tục điều chỉnh bộ sưu tập tĩnh của từ điển hoặc mảng. Kết quả là, nó phổ biến cho tôi để xem cấu trúc dữ liệu lớn mà mất nhiều thời gian để lấy từ một máy chủ với các lỗi sau đây:iPhone sử dụng mutexes với URL không đồng bộ yêu cầu

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.' 

này thường có nghĩa là hai yêu cầu đến máy chủ trở lại với dữ liệu đó đang cố gắng sửa đổi cùng một bộ sưu tập. Những gì tôi đang tìm kiếm là một hướng dẫn/ví dụ/sự hiểu biết về cách cấu trúc đúng mã của tôi để tránh lỗi bất lợi này. Tôi tin rằng câu trả lời đúng là mutexes, nhưng tôi chưa bao giờ cá nhân sử dụng chúng được nêu ra.

Đây là kết quả của việc yêu cầu HTTP đồng bộ với NSURLConnection và sau đó sử dụng NSNotification Trung tâm như một phương tiện của đoàn lần yêu cầu được hoàn thành. Khi kích hoạt các yêu cầu làm biến đổi cùng một tập hợp bộ sưu tập, chúng tôi sẽ nhận được các xung đột này.

Trả lời

15

Nếu nó có thể là bất kỳ dữ liệu (bao gồm các lớp học) sẽ được truy cập từ hai luồng đồng thời bạn phải thực hiện các bước để giữ cho các đồng bộ.

May mắn thay Objective-C làm cho nó ridiculously dễ dàng để làm điều này bằng cách sử dụng từ khóa synchronized. Từ khóa này lấy làm đối số cho bất kỳ đối tượng Objective-C nào. Bất kỳ chủ đề nào khác chỉ định cùng một đối tượng trong phần được đồng bộ sẽ tạm dừng cho đến khi kết thúc đầu tiên.

-(void) doSomethingWith:(NSArray*)someArray 
{  
    // the synchronized keyword prevents two threads ever using the same variable 
    @synchronized(someArray) 
    { 
     // modify array 
    } 
} 

Nếu bạn cần bảo vệ nhiều hơn một biến, bạn nên cân nhắc sử dụng semaphore đại diện cho quyền truy cập vào tập hợp dữ liệu đó.

// Get the semaphore. 
id groupSemaphore = [Group semaphore]; 

@synchronized(groupSemaphore) 
{ 
    // Critical group code. 
} 
28

Có một số cách để thực hiện việc này. Trường hợp đơn giản nhất trong trường hợp của bạn có thể là sử dụng chỉ thị @synchronized. Điều này sẽ cho phép bạn tạo ra một mutex trên bay bằng cách sử dụng một đối tượng tùy ý như khóa.

@synchronized(sStaticData) { 
    // Do something with sStaticData 
} 

Một cách khác là sử dụng lớp NSLock. Tạo khóa bạn muốn sử dụng, và sau đó bạn sẽ có một chút linh hoạt hơn khi nói đến việc mua mutex (đối với việc chặn nếu khóa không có sẵn, vv).

NSLock *lock = [[NSLock alloc] init]; 
// ... later ... 
[lock lock]; 
// Do something with shared data 
[lock unlock]; 
// Much later 
[lock release], lock = nil; 

Nếu bạn quyết định thực hiện một trong các cách tiếp cận này, bạn cần phải có khóa cho cả đọc và viết vì bạn đang sử dụng NSMutableArray/Set/bất kỳ cửa hàng dữ liệu nào. Như bạn đã thấy NSFastEnumeration cấm đột biến của đối tượng được liệt kê.

Nhưng tôi nghĩ một vấn đề khác ở đây là lựa chọn cấu trúc dữ liệu trong môi trường nhiều luồng. Có cần thiết phải truy cập các từ điển/mảng từ nhiều chủ đề không? Hoặc các chủ đề nền có thể kết hợp dữ liệu mà chúng nhận được và sau đó chuyển nó vào luồng chính mà chỉ là luồng duy nhất được phép truy cập dữ liệu?

+0

Vấn đề là, chủ đề 'nền' không được tạo ra rõ ràng bởi tôi. Đó là kết quả của các yêu cầu NSURLConnection không đồng bộ. Tôi không có cách nào để nói chuyện với chủ đề chính thông qua mã. Đề xuất khác của bạn là hữu ích mặc dù và tôi đánh giá cao nó. – Coocoo4Cocoa

+0

Tôi tin rằng đại biểu cho NSURLConnection sẽ được gọi trên luồng bắt đầu quá trình tải, không nhất thiết là chuỗi tạo đối tượng. Vì vậy, bạn có thể kết hợp dữ liệu trong các phương thức đại biểu của mình. – sbooth

+0

Có! cảm ơn nó làm việc cho tôi. – Armanoide

0

Để đối phó với sStaticData và NSLock câu trả lời (bình luận được giới hạn 600 ký tự), bạn không cần phải rất cẩn thận về việc tạo các sStaticData và các đối tượng NSLock trong một cách an toàn ren (để tránh sự kịch bản rất khó của nhiều ổ khóa được tạo ra bởi các chủ đề khác nhau)?

Tôi nghĩ có hai cách giải quyết:

1) Bạn có thể uỷ quyền cho các đối tượng được tạo ra vào đầu ngày trong thread gốc duy nhất.

2) Xác định đối tượng tĩnh được tạo tự động vào đầu ngày để sử dụng làm khóa, ví dụ: một NSString tĩnh có thể được tạo ra inline:

static NSString *sMyLock1 = @"Lock1"; 

Sau đó, tôi nghĩ bạn có thể an toàn sử dụng

@synchronized(sMyLock1) 
{ 
    // Stuff 
} 

Nếu không tôi nghĩ rằng bạn sẽ luôn luôn kết thúc trong một 'gà và quả trứng' tình với việc tạo ổ khóa của bạn trong một chủ đề an toàn?

Tất nhiên, bạn rất khó có thể gặp phải bất kỳ vấn đề nào vì hầu hết các ứng dụng iPhone chạy trong một chuỗi.

Tôi không biết về đề xuất [Nhóm semaphore] trước đó, đó cũng có thể là giải pháp.

+0

“Câu trả lời trước”? Có hai câu trả lời khác ngay bây giờ. –

+0

Tôi bây giờ đã cập nhật nó để làm rõ (FTR, tôi có nghĩa là câu trả lời ở trên này - cho tôi đề cập đến sStaticData và NSLock không có nhiều sự mơ hồ)! –

0

N.B. Nếu bạn đang sử dụng đồng bộ hóa không quên thêm -fobjc-exceptions để cờ GCC của bạn: “Xử lý ngoại lệ”

Objective-C cung cấp hỗ trợ để đồng bộ hóa thread và ngoại lệ xử lý, được giải thích trong bài viết này và Để bật hỗ trợ cho các tính năng này, sử dụng chuyển đổi -fobjc-ngoại lệ của Bộ sưu tập trình biên dịch GNU (GCC) phiên bản 3.3 trở lên.

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

0

Sử dụng một bản sao của đối tượng để sửa đổi nó. Vì bạn đang cố gắng sửa đổi tham chiếu của một mảng (bộ sưu tập), trong khi một người khác cũng có thể sửa đổi nó (nhiều quyền truy cập), việc tạo một bản sao sẽ làm việc cho bạn. Tạo một bản sao và sau đó liệt kê trên bản sao đó.

NSMutableArray *originalArray = @[@"A", @"B", @"C"]; 
NSMutableArray *arrayToEnumerate = [originalArray copy]; 

Bây giờ sửa đổi mảngToEnumerate. Vì nó không được tham chiếu đến originalArray, nhưng là một bản sao của originalArray, nó sẽ không gây ra vấn đề.

0

Có nhiều cách khác nếu bạn không muốn chi phí khóa vì nó có chi phí. Thay vì sử dụng một khóa để bảo vệ tài nguyên được chia sẻ (trong trường hợp của bạn nó có thể là từ điển hoặc mảng), bạn có thể tạo một hàng đợi để nối tiếp nhiệm vụ đang truy cập vào mã phần quan trọng của bạn. Hàng đợi không có cùng số lượng hình phạt như ổ khóa vì nó không yêu cầu bẫy vào hạt nhân để có được mutex. chỉ cần đặt

dispatch_async(serial_queue, ^{ 
    <#critical code#> 
}) 

Trong trường hợp nếu bạn muốn thực hiện hiện tại để chờ cho đến khi công việc hoàn tất, bạn có thể sử dụng

dispatch_sync(serial_queue Or concurrent, ^{ 
    <#critical code#> 
}) 

Nói chung nếu thực hiện doest cần không phải chờ đợi, không đồng bộ là một cách ưa thích làm.

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