Sự khác biệt giữa các thuộc tính nguyên tử và phi nguyên tử là gì?


Câu trả lời:


1761

Hai cái cuối giống hệt nhau; "nguyên tử" là hành vi mặc định ( lưu ý rằng nó không thực sự là một từ khóa; nó chỉ được chỉ định bởi sự vắng mặtnonatomic - atomicđã được thêm làm từ khóa trong các phiên bản gần đây của llvm / clang).

Giả sử rằng bạn đang @synthesizing việc triển khai phương thức, nguyên tử so với phi nguyên tử thay đổi mã được tạo. Nếu bạn đang viết setter / getters của riêng bạn, nguyên tử / khôngatomic / giữ / gán / sao chép chỉ là tư vấn. (Lưu ý: @synthesize bây giờ là hành vi mặc định trong các phiên bản gần đây của LLVM Ngoài ra còn có không cần phải biến dụ khai báo, họ sẽ được tổng hợp tự động, quá, và sẽ có một. _Thêm vào phía trước tên của họ để ngăn chặn truy cập trực tiếp tình cờ).

Với "nguyên tử", setter / getter được tổng hợp sẽ đảm bảo rằng toàn bộ giá trị luôn được trả về từ getter hoặc được đặt bởi setter, bất kể hoạt động setter trên bất kỳ luồng nào khác. Nghĩa là, nếu luồng A ở giữa getter trong khi luồng B gọi setter, một giá trị khả thi thực tế - một đối tượng tự động phát hành, rất có thể - sẽ được trả về cho người gọi trong A.

Trong nonatomic, không có đảm bảo như vậy được thực hiện. Do đó, nonatomicnhanh hơn đáng kể so với "nguyên tử".

Điều "nguyên tử" không làm là đảm bảo an toàn cho luồng. Nếu luồng A đang gọi getter đồng thời với luồng B và C gọi setter với các giá trị khác nhau, thì luồng A có thể nhận được bất kỳ một trong ba giá trị nào được trả về - một giá trị trước bất kỳ setters nào được gọi hoặc một trong hai giá trị được truyền vào setters trong B và C. Tương tự như vậy, đối tượng có thể kết thúc bằng giá trị từ B hoặc C, không có cách nào để nói.

Đảm bảo tính toàn vẹn dữ liệu - một trong những thách thức chính của lập trình đa luồng - đạt được bằng các phương tiện khác.

Thêm vào đây:

atomicity của một thuộc tính duy nhất cũng không thể đảm bảo an toàn luồng khi có nhiều thuộc tính phụ thuộc đang hoạt động.

Xem xét:

 @property(atomic, copy) NSString *firstName;
 @property(atomic, copy) NSString *lastName;
 @property(readonly, atomic, copy) NSString *fullName;

Trong trường hợp này, luồng A có thể đổi tên đối tượng bằng cách gọi setFirstName:và sau đó gọi setLastName:. Trong khi đó, luồng B có thể gọi fullNamegiữa hai cuộc gọi của A và sẽ nhận được tên mới được ghép với tên cũ.

Để giải quyết điều này, bạn cần một mô hình giao dịch . Tức là một số loại đồng bộ hóa và / hoặc loại trừ khác cho phép một người loại trừ quyền truy cập fullNametrong khi các thuộc tính phụ thuộc đang được cập nhật.


21
Cho rằng bất kỳ mã an toàn luồng nào cũng sẽ tự khóa, khi nào bạn muốn sử dụng các bộ truy cập thuộc tính nguyên tử? Tôi đang gặp khó khăn khi nghĩ về một ví dụ tốt.
Daniel Dickison

8
@bbum Có ý nghĩa. Tôi thích bình luận của bạn cho một câu trả lời khác rằng an toàn luồng là mối quan tâm ở cấp độ mô hình. Từ định nghĩa an toàn luồng của IBM: ibm.co/yTEbjY "Nếu một lớp được triển khai chính xác, đó là một cách khác để nói rằng nó tuân thủ đặc tả của nó, không có chuỗi hoạt động (đọc hoặc ghi các trường công khai và gọi các phương thức công khai) trên các đối tượng của lớp đó sẽ có thể đưa đối tượng vào trạng thái không hợp lệ, quan sát đối tượng ở trạng thái không hợp lệ hoặc vi phạm bất kỳ bất biến, điều kiện tiên quyết hoặc hậu điều kiện nào của lớp. "
Ben Flynn

6
Đây là một ví dụ tương tự như @StevenKramer: Tôi có một @property NSArray* astronomicalEvents;danh sách liệt kê dữ liệu tôi muốn hiển thị trong UI. Khi ứng dụng khởi chạy con trỏ trỏ đến một mảng trống, thì ứng dụng sẽ lấy dữ liệu từ web. Khi yêu cầu web hoàn thành (trong một luồng khác), ứng dụng sẽ xây dựng một mảng mới sau đó về cơ bản đặt thuộc tính thành một giá trị con trỏ mới. Đó là luồng an toàn và tôi không phải viết bất kỳ mã khóa nào, trừ khi tôi thiếu thứ gì đó. Có vẻ khá hữu ích với tôi.
bugloaf

10
@HotLicks Một niềm vui khác; trên một số kiến ​​trúc nhất định (Không thể nhớ cái nào), các giá trị 64 bit được truyền dưới dạng đối số có thể được truyền một nửa trong một thanh ghi và một nửa trên ngăn xếp. atomicngăn chặn đọc nửa giá trị chủ đề chéo. (Đó là một lỗi thú vị để theo dõi.)
bbum 23/11/13

8
@congliu Chủ đề A trả về một đối tượng không có retain/autoreleasenhảy. Chủ đề B phát hành đối tượng. Chủ đề A đi bùng nổ . atomicđảm bảo rằng luồng A có tham chiếu mạnh (số giữ lại +1) cho giá trị trả về.
bbum

360

Điều này được giải thích trong tài liệu của Apple , nhưng dưới đây là một số ví dụ về những gì đang thực sự xảy ra.

Lưu ý rằng không có từ khóa "nguyên tử", nếu bạn không chỉ định "không nguyên tử", thì thuộc tính là nguyên tử, nhưng chỉ định rõ ràng "nguyên tử" sẽ dẫn đến lỗi.

Nếu bạn không chỉ định "nonatomic", thì thuộc tính là nguyên tử, nhưng bạn vẫn có thể chỉ định rõ ràng "nguyên tử" trong các phiên bản gần đây nếu bạn muốn.

//@property(nonatomic, retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

Bây giờ, biến thể nguyên tử phức tạp hơn một chút:

//@property(retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName_ retain];
      [userName release];
      userName = userName_;
    }
}

Về cơ bản, phiên bản nguyên tử phải có khóa để đảm bảo an toàn cho luồng, và cũng đang phá vỡ số lượng ref trên đối tượng (và số lượng autorelease để cân bằng nó) để đối tượng được đảm bảo tồn tại cho người gọi, nếu không thì là một điều kiện cuộc đua tiềm năng nếu một luồng khác đang đặt giá trị, làm cho số lượng ref giảm xuống 0.

Thực tế, có một số lượng lớn các biến thể khác nhau về cách thức những thứ này hoạt động tùy thuộc vào việc các thuộc tính là giá trị vô hướng hay đối tượng và cách giữ, sao chép, chỉ đọc, không tương tác, v.v. Nói chung, các trình tổng hợp thuộc tính chỉ cần biết cách thực hiện "đúng" cho tất cả các kết hợp.


8
@Louis Gerbarg: Tôi tin rằng phiên bản trình cài đặt (không biến đổi, giữ lại) của bạn sẽ không hoạt động đúng nếu bạn cố gán cùng một đối tượng (đó là: userName == userName_)
Florin

5
Mã của bạn hơi sai lệch; không có đảm bảo về những gì getters / setters nguyên tử được đồng bộ hóa. Quan trọng, @property (assign) id delegate;không được đồng bộ hóa trên bất cứ điều gì (iOS SDK GCC 4.2 ARM -Os), có nghĩa là có một cuộc đua giữa [self.delegate delegateMethod:self];foo.delegate = nil; self.foo = nil; [super dealloc];. Xem stackoverflow.com/questions/917884/
tc.

@fyolquer Tôi không chắc chắn những gì _val/ vallà, nhưng không, không thực sự. Trình getter cho một nguyên tử copy/ thuộc retaintính cần phải đảm bảo rằng nó không trả về một đối tượng có số lần trả về bằng 0 do setter được gọi trong một luồng khác, điều đó có nghĩa là nó cần phải đọc ngà, giữ nó trong khi đảm bảo rằng setter không ghi đè và phát hành nó, và sau đó tự động giải phóng nó để cân bằng việc giữ lại. Điều đó về cơ bản có nghĩa là cả getter và setter đều phải sử dụng khóa (nếu bố trí bộ nhớ đã được sửa, nó có thể thực hiện được với các lệnh CAS2; than ôi -retainlà một cuộc gọi phương thức).
tc.

@tc Đã khá lâu rồi nhưng ý tôi muốn viết có lẽ là thế này: gist.github.com/fjolnir/5d96b3272c6255f6baae Nhưng vâng, người đọc có thể đọc được giá trị cũ trước khi setFoo: trả về người đọc trả lại nó. Nhưng có lẽ nếu setter sử dụng -autorelease thay vì -release, điều đó sẽ khắc phục điều đó.
Fjölnir

@fyolquer Thật không may, không: Đó là autorelease trên luồng của setter, trong khi nó cần phải được autorelease trên luồng của getter. Có vẻ như có một cơ hội (mỏng) sắp hết vì bạn đang sử dụng đệ quy.
tc.

170

Nguyên tử

  • là hành vi mặc định
  • sẽ đảm bảo quy trình hiện tại được CPU hoàn thành, trước khi quy trình khác truy cập vào biến
  • không nhanh, vì nó đảm bảo quá trình hoàn thành

Không nguyên tử

  • KHÔNG phải là hành vi mặc định
  • nhanh hơn (đối với mã được tổng hợp, nghĩa là, đối với các biến được tạo bằng @property và @synthesize)
  • không an toàn chủ đề
  • có thể dẫn đến hành vi không mong muốn, khi hai quá trình khác nhau truy cập cùng một biến cùng một lúc

137

Cách tốt nhất để hiểu sự khác biệt là sử dụng ví dụ sau.

Giả sử có một thuộc tính chuỗi nguyên tử được gọi là "name" và nếu bạn gọi [self setName:@"A"]từ luồng A, gọi [self setName:@"B"]từ luồng B và gọi [self name]từ luồng C, thì tất cả các hoạt động trên các luồng khác nhau sẽ được thực hiện một cách thanh thản, có nghĩa là nếu một luồng đang thực hiện một setter hoặc getter, sau đó các chủ đề khác sẽ chờ.

Điều này làm cho tài sản "tên" đọc / ghi an toàn, nhưng nếu một luồng khác, D, gọi [name release]đồng thời thì thao tác này có thể tạo ra sự cố do không có cuộc gọi setter / getter nào liên quan ở đây. Điều đó có nghĩa là một đối tượng được đọc / ghi an toàn (ATOMIC), nhưng không an toàn luồng vì các luồng khác có thể đồng thời gửi bất kỳ loại tin nhắn nào đến đối tượng. Các nhà phát triển nên đảm bảo an toàn luồng cho các đối tượng như vậy.

Nếu "tên" thuộc tính là không biến đổi, thì tất cả các luồng trong ví dụ trên - A, B, C và D sẽ thực thi đồng thời tạo ra bất kỳ kết quả không thể đoán trước nào. Trong trường hợp nguyên tử, một trong hai A, B hoặc C sẽ thực thi trước, nhưng D vẫn có thể thực thi song song.


116

Cú pháp và ngữ nghĩa đã được xác định rõ bởi các câu trả lời xuất sắc khác cho câu hỏi này. Bởi vì thực thihiệu suất không chi tiết tốt, tôi sẽ thêm câu trả lời của tôi.

Sự khác biệt chức năng giữa 3 là gì?

Tôi luôn coi nguyên tử là một mặc định khá tò mò. Ở cấp độ trừu tượng mà chúng tôi làm việc, sử dụng các thuộc tính nguyên tử cho một lớp làm phương tiện để đạt được 100% an toàn luồng là một trường hợp góc. Đối với các chương trình đa luồng thực sự chính xác, sự can thiệp của lập trình viên gần như chắc chắn là một yêu cầu. Trong khi đó, đặc điểm hiệu suất và thực hiện vẫn chưa được chi tiết chuyên sâu. Đã viết một số chương trình đa luồng rất nhiều trong những năm qua, tôi đã tuyên bố nonatomictoàn bộ tài sản của mình vì nguyên tử không hợp lý cho bất kỳ mục đích nào. Trong quá trình thảo luận về các chi tiết về tính chất nguyên tử và phi nguyên tử của câu hỏi này , tôi đã thực hiện một số hồ sơ gặp phải một số kết quả gây tò mò.

Chấp hành

Đồng ý. Điều đầu tiên tôi muốn làm rõ là việc thực hiện khóa được xác định và trừu tượng hóa việc thực hiện. Louis sử dụng @synchronized(self)trong ví dụ của mình - tôi đã xem đây là một nguồn gây nhầm lẫn phổ biến. Việc thực hiện không thực sự sử dụng @synchronized(self); nó sử dụng khóa spin cấp đối tượng . Minh họa của Louis là tốt cho một minh họa cấp cao bằng cách sử dụng các cấu trúc mà chúng ta đều quen thuộc, nhưng điều quan trọng là phải biết nó không sử dụng @synchronized(self).

Một sự khác biệt nữa là các thuộc tính nguyên tử sẽ giữ lại / giải phóng chu kỳ các đối tượng của bạn trong getter.

Hiệu suất

Đây là phần thú vị: Hiệu suất sử dụng truy cập thuộc tính nguyên tử trong các trường hợp không kiểm chứng (ví dụ: đơn luồng) có thể thực sự rất nhanh trong một số trường hợp. Trong các trường hợp ít hơn lý tưởng, việc sử dụng truy cập nguyên tử có thể tốn hơn 20 lần chi phí nonatomic. Trong khi trường hợp Contested sử dụng 7 luồng thì chậm hơn 44 lần đối với cấu trúc ba byte ( Core i7 Quad Core 2.2 GHz , x86_64). Cấu trúc ba byte là một ví dụ về thuộc tính rất chậm.

Lưu ý bên lề thú vị: Các bộ truy cập do người dùng định nghĩa của cấu trúc ba byte nhanh hơn 52 lần so với các bộ truy cập nguyên tử tổng hợp; hoặc 84% tốc độ của các phụ kiện không phải là tổng hợp.

Đối tượng trong các trường hợp tranh cãi cũng có thể vượt quá 50 lần.

Do số lượng tối ưu hóa và các biến thể trong việc triển khai, khá khó để đo lường các tác động trong thế giới thực trong các bối cảnh này. Bạn có thể thường nghe một cái gì đó như "Tin tưởng nó, trừ khi bạn hồ sơ và thấy nó là một vấn đề". Do mức độ trừu tượng, thực sự khá khó để đo lường tác động thực tế. Lượm lặt chi phí thực tế từ hồ sơ có thể rất tốn thời gian, và do trừu tượng, khá không chính xác. Đồng thời, ARC vs MRC có thể tạo ra sự khác biệt lớn.

Vì vậy, hãy lùi lại, không tập trung vào việc thực hiện truy cập tài sản, chúng tôi sẽ bao gồm các nghi phạm thông thường như objc_msgSend, và kiểm tra một số kết quả cấp cao trong thế giới thực cho nhiều cuộc gọi đến người nhận NSStringtrong các trường hợp không kiểm chứng (giá trị tính bằng giây):

  • MRC | nonatomic | thủ công getters: 2
  • MRC | nonatomic | tổng hợp getter: 7
  • MRC | nguyên tử | tổng hợp getter: 47
  • ARC | nonatomic | getter tổng hợp: 38 (lưu ý: ARC's thêm số đếm ref ở đây)
  • ARC | nguyên tử | tổng hợp getter: 47

Như bạn có thể đoán, hoạt động đếm / đạp xe tham chiếu là một đóng góp đáng kể với nguyên tử và theo ARC. Bạn cũng sẽ thấy sự khác biệt lớn hơn trong các trường hợp tranh cãi.

Mặc dù tôi rất chú ý đến hiệu suất, tôi vẫn nói ngữ nghĩa đầu tiên! . Trong khi đó, hiệu suất là ưu tiên thấp cho nhiều dự án. Tuy nhiên, biết chi tiết thực hiện và chi phí của các công nghệ bạn sử dụng chắc chắn không gây hại. Bạn nên sử dụng công nghệ phù hợp với nhu cầu, mục đích và khả năng của mình. Hy vọng rằng điều này sẽ giúp bạn tiết kiệm một vài giờ so sánh, và giúp bạn đưa ra quyết định sáng suốt hơn khi thiết kế chương trình của mình.


MRC | nguyên tử | tổng hợp getter: 47 ARC | nguyên tử | getter tổng hợp: 47 Điều gì làm cho chúng giống nhau? Không nên ARC có nhiều chi phí hơn?
SDEZero

2
Vì vậy, nếu tính chất nguyên tử là xấu y thì chúng mặc định. Để tăng mã soạn sẵn?
Kunal Balani

@ LearnCocos2D tôi mới thử nghiệm vào ngày 10.8.5 trên cùng một máy, nhắm mục tiêu 10.8, cho trường hợp không được kiểm tra đơn luồng với một trường hợp NSStringkhông bất tử: -ARC atomic (BASELINE): 100% -ARC nonatomic, synthesised: 94% -ARC nonatomic, user defined: 86% -MRC nonatomic, user defined: 5% -MRC nonatomic, synthesised: 19% -MRC atomic: 102%- kết quả ngày nay hơi khác. Tôi đã không làm bất kỳ @synchronizedso sánh. @synchronizedlà khác nhau về mặt ngữ nghĩa và tôi không coi đó là một công cụ tốt nếu bạn có các chương trình đồng thời không cần thiết. Nếu bạn cần tốc độ, tránh @synchronized.
justin

Bạn có bài kiểm tra này trực tuyến ở đâu đó không? Tôi tiếp tục thêm của tôi ở đây: github.com/LearnCocos2D/LearnCocos2D/tree/master/iêu
LearnCocos2D

@ LearnCocos2D tôi chưa chuẩn bị chúng cho con người, xin lỗi.
justin

95

Nguyên tử = an toàn luồng

Không nguyên tử = Không an toàn luồng

An toàn chủ đề:

Các biến sơ thẩm là an toàn luồng nếu chúng hoạt động chính xác khi được truy cập từ nhiều luồng, bất kể lập lịch hoặc xen kẽ việc thực thi các luồng đó bởi môi trường thời gian chạy và không có đồng bộ hóa bổ sung hoặc phối hợp khác trên một phần của mã gọi.

Trong bối cảnh của chúng tôi:

Nếu một luồng thay đổi giá trị của thể hiện thì giá trị thay đổi có sẵn cho tất cả các luồng và chỉ một luồng có thể thay đổi giá trị tại một thời điểm.

Nơi sử dụng atomic:

nếu biến thể hiện sẽ được truy cập trong môi trường đa luồng.

Hàm ý của atomic:

Không nhanh như nonatomicbởi vì nonatomickhông yêu cầu bất kỳ cơ quan giám sát nào làm việc trên đó từ thời gian chạy.

Sử dụng ở đâu nonatomic:

Nếu biến đối tượng sẽ không bị thay đổi bởi nhiều luồng, bạn có thể sử dụng nó. Nó cải thiện hiệu suất.


3
Tất cả những gì bạn nói ở đây là đúng, nhưng câu cuối cùng về cơ bản là "sai", Dura, cho lập trình ngày nay. Thật không thể tin được là bạn sẽ cố gắng "cải thiện hiệu suất" theo cách này. (Ý tôi là, trước khi bạn hiểu rõ về điều đó, bạn sẽ "không sử dụng ARC", "không sử dụng NSString vì nó chậm!" V.v.) Để làm một ví dụ cực đoan, nó sẽ giống như nói "đội, đừng đưa bất kỳ bình luận nào vào mã, vì nó làm chúng ta chậm lại. " Không có đường ống phát triển thực tế nơi bạn muốn đạt được hiệu suất lý thuyết (không tồn tại) vì lý do không đáng tin cậy.
Fattie

3
@JoeBlow thực tế của bạn, bạn có thể xác minh nó ở đây developer.apple.com/l
Library / mac / documentation / Coloa / Conceptionual / Lỗi

1
Durai, FWIW, liên kết đó mâu thuẫn trực tiếp với luận điểm của bạn về Nguyên tử = chủ đề an toàn. Trong tài liệu Apple nói rõ ràng, tính nguyên tử của Tài sản không đồng nghĩa với sự an toàn của luồng đối tượng. Trong thực tế, nguyên tử hiếm khi đủ để đạt được an toàn luồng.
Cướp

69

Tôi tìm thấy một lời giải thích khá tốt về các tính chất nguyên tử và phi nguyên tử ở đây . Đây là một số văn bản có liên quan từ cùng:

"Nguyên tử" có nghĩa là nó không thể bị phá vỡ. Trong thuật ngữ lập trình / hệ điều hành, một cuộc gọi chức năng nguyên tử là một cuộc gọi không thể bị gián đoạn - toàn bộ chức năng phải được thực thi và không được hoán đổi khỏi CPU bằng cách chuyển đổi ngữ cảnh thông thường của hệ điều hành cho đến khi hoàn thành. Chỉ trong trường hợp bạn không biết: vì CPU chỉ có thể làm một việc tại một thời điểm, HĐH sẽ chuyển quyền truy cập vào CPU cho tất cả các quy trình đang chạy trong các lát cắt nhỏ, để tạo ảo giácđa nhiệm. Bộ lập lịch CPU có thể (và không) làm gián đoạn một quá trình tại bất kỳ điểm nào trong quá trình thực thi của nó - ngay cả trong lệnh gọi hàm giữa. Vì vậy, đối với các hành động như cập nhật các biến đếm được chia sẻ trong đó hai quá trình có thể cố gắng cập nhật biến cùng một lúc, chúng phải được thực thi 'nguyên tử', tức là, mỗi hành động cập nhật phải kết thúc toàn bộ trước khi bất kỳ quá trình nào khác có thể được hoán đổi trên CPU.

Vì vậy, tôi đoán rằng nguyên tử trong trường hợp này có nghĩa là các phương thức đọc thuộc tính không thể bị gián đoạn - thực tế có nghĩa là (các) biến được đọc bởi phương thức có thể thay đổi nửa giá trị của chúng bởi vì một số luồng / lệnh / hàm khác bị lỗi hoán đổi trên CPU.

Bởi vì các atomicbiến không thể bị gián đoạn, giá trị được chứa bởi chúng tại bất kỳ điểm nào là (khóa luồng) được đảm bảo không bị lỗi , mặc dù vậy, đảm bảo khóa luồng này làm cho việc truy cập vào chúng chậm hơn. non-atomicmặt khác, các biến không đảm bảo như vậy nhưng cung cấp sự sang trọng của việc truy cập nhanh hơn. Để tổng hợp, hãy đi cùng non-atomickhi bạn biết các biến của mình sẽ không được truy cập bởi nhiều luồng đồng thời và tăng tốc mọi thứ.


1
Liên kết bị hỏng. ; (
Rob

Đó là vấn đề với các liên kết :( may mắn thay, tôi đã trích dẫn văn bản có liên quan trong câu trả lời của mình
tipycalFlow

67

Sau khi đọc rất nhiều bài viết, bài viết Stack Overflow và tạo các ứng dụng demo để kiểm tra các thuộc tính biến, tôi quyết định đặt tất cả các thông tin thuộc tính lại với nhau:

  1. atomic // Mặc định
  2. nonatomic
  3. strong = retain // Mặc định
  4. weak = unsafe_unretained
  5. retain
  6. assign // Mặc định
  7. unsafe_unretained
  8. copy
  9. readonly
  10. readwrite // Mặc định

Trong bài viết Thuộc tính biến đổi hoặc thuộc tính sửa đổi trong iOS, bạn có thể tìm thấy tất cả các thuộc tính được đề cập ở trên và điều đó chắc chắn sẽ giúp bạn.

  1. atomic

    • atomic có nghĩa là chỉ có một luồng truy cập biến (kiểu tĩnh).
    • atomic là chủ đề an toàn.
    • Nhưng nó là hiệu suất chậm
    • atomic là hành vi mặc định
    • Các bộ truy cập nguyên tử trong môi trường không được thu gom rác (tức là khi sử dụng giữ lại / giải phóng / tự động chạy) sẽ sử dụng khóa để đảm bảo rằng một luồng khác không can thiệp vào cài đặt / nhận giá trị chính xác.
    • Nó không thực sự là một từ khóa.

    Thí dụ:

        @property (retain) NSString *name;
    
        @synthesize name;
  2. nonatomic

    • nonatomic có nghĩa là nhiều luồng truy cập biến (loại động).
    • nonatomic là chủ đề không an toàn.
    • Nhưng nó là hiệu suất nhanh
    • nonatomicKHÔNG phải là hành vi mặc định. Chúng ta cần thêm nonatomictừ khóa trong thuộc tính property.
    • Nó có thể dẫn đến hành vi không mong muốn, khi hai quá trình (luồng) khác nhau truy cập cùng một biến cùng một lúc.

    Thí dụ:

        @property (nonatomic, retain) NSString *name;
    
        @synthesize name;

Làm thế nào có thể gán và mạnh / giữ cả hai được mặc định?
BangOperator

mạnh mẽ đi kèm với ARC, giữ lại được mặc định trước ARC
abdullahselek

56

Nguyên tử:

Nguyên tử đảm bảo rằng quyền truy cập vào tài sản sẽ được thực hiện theo cách nguyên tử. Ví dụ, nó luôn trả về một đối tượng được khởi tạo đầy đủ, bất kỳ get / set thuộc tính nào trên một luồng phải hoàn thành trước khi một đối tượng khác có thể truy cập nó.

Nếu bạn tưởng tượng chức năng sau đây xảy ra trên hai luồng cùng một lúc, bạn có thể thấy tại sao kết quả sẽ không đẹp.

-(void) setName:(NSString*)string
{
  if (name)
  {
    [name release]; 
    // what happens if the second thread jumps in now !?
    // name may be deleted, but our 'name' variable is still set!
    name = nil;
  }

  ...
}

Ưu điểm: Trả về các đối tượng được khởi tạo hoàn toàn mỗi lần làm cho nó trở thành lựa chọn tốt nhất trong trường hợp đa luồng.

Nhược điểm: Hiệu suất đạt, làm cho việc thực thi chậm hơn một chút

Phi nguyên tử:

Không giống như Atomic, nó không đảm bảo trả lại đối tượng được khởi tạo đầy đủ mỗi lần.

Ưu điểm: Thực hiện cực nhanh.

Nhược điểm: Cơ hội giá trị rác trong trường hợp đa luồng.


5
Nhận xét đó không có nhiều ý nghĩa. Bạn có thể làm rõ? Nếu bạn xem các ví dụ trên trang web của Apple thì từ khóa nguyên tử sẽ đồng bộ hóa trên đối tượng trong khi cập nhật các thuộc tính của nó.
Andrew Grant

52

Câu trả lời dễ nhất trước: Không có sự khác biệt giữa hai ví dụ thứ hai của bạn. Theo mặc định, người truy cập tài sản là nguyên tử.

Các bộ truy cập nguyên tử trong môi trường không được thu gom rác (tức là khi sử dụng giữ lại / giải phóng / tự động chạy) sẽ sử dụng khóa để đảm bảo rằng một luồng khác không can thiệp vào cài đặt / nhận giá trị chính xác.

Xem phần " Hiệu suất và phân luồng " trong tài liệu Objective-C 2.0 của Apple để biết thêm thông tin và các cân nhắc khác khi tạo ứng dụng đa luồng.


8
Hai lý do. Trước hết, đối với mã tổng hợp, nó tạo ra nhanh hơn (nhưng không phải mã luồng an toàn). Thứ hai, nếu bạn đang viết các bộ truy cập khách hàng không phải là nguyên tử, nó cho phép bạn chú thích cho bất kỳ người dùng nào trong tương lai rằng mã không phải là nguyên tử khi họ đang đọc giao diện của nó, mà không thực hiện chúng.
Louis Gerbarg


31

Nguyên tử có nghĩa là chỉ có một luồng truy cập biến (kiểu tĩnh). Nguyên tử là an toàn chủ đề, nhưng nó là chậm.

Nonatomic có nghĩa là nhiều luồng truy cập vào biến (kiểu động). Nonatomic là chủ đề không an toàn, nhưng nó là nhanh chóng.


14

Nguyên tử là luồng an toàn , nó chậmđảm bảo tốt (không được bảo đảm) rằng chỉ có giá trị bị khóa được cung cấp cho dù có bao nhiêu luồng đang cố truy cập trên cùng một vùng. Khi sử dụng nguyên tử, một đoạn mã được viết bên trong hàm này trở thành một phần của phần quan trọng, chỉ có một luồng có thể thực thi tại một thời điểm.

Nó chỉ đảm bảo an toàn chủ đề; nó không đảm bảo điều đó Ý tôi là bạn thuê một người lái xe chuyên nghiệp cho chiếc xe của bạn, nhưng điều đó không đảm bảo chiếc xe sẽ không gặp tai nạn. Tuy nhiên, xác suất vẫn là nhỏ nhất.

Nguyên tử - nó không thể bị phá vỡ, vì vậy kết quả được mong đợi. Với nonatomic - khi một luồng khác truy cập vào vùng nhớ, nó có thể sửa đổi nó, vì vậy kết quả thật bất ngờ.

Mã nói chuyện:

Nguyên tử làm cho getter và setter của luồng thuộc tính an toàn. ví dụ nếu bạn đã viết:

self.myProperty = value;

là chủ đề an toàn.

[myArray addObject:@"Abc"] 

KHÔNG phải là chủ đề an toàn.


Tôi không biết đoạn cuối đến như thế nào, nhưng nó đơn giản sai, không có thứ gọi là "bản sao riêng tư".
cao điểm

13

Không có từ khóa "nguyên tử" như vậy

@property(atomic, retain) UITextField *userName;

Chúng ta có thể sử dụng như trên

@property(retain) UITextField *userName;

Xem câu hỏi về Stack Overflow Tôi đang gặp vấn đề nếu tôi sử dụng @property (nguyên tử, giữ lại) NSString * myString .


10
"Có từ khóa như vậy", rằng từ khóa không được yêu cầu theo mặc định và thậm chí là giá trị mặc định không có nghĩa là từ khóa không tồn tại.
Matthijn

4
Điều này là không chính xác. Các từ khóa không tồn tại. Câu trả lời này là sai lệch, và tôi sẽ khuyến khích đưa nó xuống.
sethfri

12

nguyên tử (mặc định)

Nguyên tử là mặc định: nếu bạn không gõ bất cứ thứ gì, tài sản của bạn là nguyên tử. Một thuộc tính nguyên tử được đảm bảo rằng nếu bạn cố đọc từ nó, bạn sẽ nhận lại giá trị hợp lệ. Nó không đảm bảo về giá trị đó có thể là gì, nhưng bạn sẽ lấy lại được dữ liệu tốt, không chỉ bộ nhớ rác. Điều này cho phép bạn làm là nếu bạn có nhiều luồng hoặc nhiều tiến trình trỏ đến một biến duy nhất, một luồng có thể đọc và luồng khác có thể viết. Nếu chúng đánh cùng một lúc, luồng người đọc được đảm bảo có được một trong hai giá trị: trước khi thay đổi hoặc sau khi thay đổi. Những gì nguyên tử không cung cấp cho bạn là bất kỳ loại đảm bảo nào về những giá trị nào bạn có thể nhận được. Nguyên tử thực sự thường bị nhầm lẫn với an toàn luồng, và điều đó không chính xác. Bạn cần đảm bảo an toàn chủ đề của bạn theo những cách khác.

nonatomic

Mặt khác, phi nguyên tử, như bạn có thể đoán, chỉ có nghĩa là, đừng làm điều đó nguyên tử. Những gì bạn mất là đảm bảo rằng bạn luôn nhận lại được một cái gì đó. Nếu bạn cố đọc ở giữa một bản ghi, bạn có thể lấy lại dữ liệu rác. Nhưng mặt khác, bạn đi nhanh hơn một chút. Bởi vì các thuộc tính nguyên tử phải thực hiện một số phép thuật để đảm bảo rằng bạn sẽ lấy lại giá trị, chúng chậm hơn một chút. Nếu đó là một tài sản mà bạn đang truy cập rất nhiều, bạn có thể muốn thả xuống không phổ biến để đảm bảo rằng bạn không phải chịu hình phạt tốc độ đó.

Xem thêm tại đây: https://realm.io/news/tmi-objective-c-property-attribut/


11

Các mặc địnhatomic , phương tiện này nó không chi phí bạn thực hiện bất cứ khi nào bạn sử dụng tài sản, nhưng nó là chủ đề an toàn. Objective-C làm gì, được đặt khóa, vì vậy chỉ luồng thực sự mới có thể truy cập vào biến, miễn là setter / getter được thực thi.

Ví dụ với MRC của một tài sản có ngà _i Internalal:

[_internal lock]; //lock
id result = [[value retain] autorelease];
[_internal unlock];
return result;

Vì vậy, hai cuối cùng là như nhau:

@property(atomic, retain) UITextField *userName;

@property(retain) UITextField *userName; // defaults to atomic

Mặt khác không nonatomicthêm gì vào mã của bạn. Vì vậy, nó chỉ là chủ đề an toàn nếu bạn mã cơ chế bảo mật.

@property(nonatomic, retain) UITextField *userName;

Các từ khóa không phải được viết là thuộc tính đầu tiên.

Đừng quên, điều này không có nghĩa là toàn bộ tài sản là an toàn cho chuỗi. Chỉ có lời gọi phương thức của setter / getter là. Nhưng nếu bạn sử dụng một setter và sau đó một getter cùng lúc với 2 luồng khác nhau, nó cũng có thể bị hỏng!


10

Trước khi bạn bắt đầu: Bạn phải biết rằng mọi đối tượng trong bộ nhớ cần phải được giải phóng khỏi bộ nhớ để một nhà văn mới xảy ra. Bạn không thể chỉ đơn giản là viết lên trên một cái gì đó như bạn làm trên giấy. Trước tiên bạn phải xóa (dealloc) nó và sau đó bạn có thể viết lên nó. Nếu tại thời điểm xóa được thực hiện (hoặc hoàn thành một nửa) và chưa có gì được viết (hoặc một nửa đã viết) và bạn cố gắng đọc nó có thể rất có vấn đề! Nguyên tử và không gây dị ứng giúp bạn điều trị vấn đề này theo nhiều cách khác nhau.

Đầu tiên đọc câu hỏi này và sau đó đọc câu trả lời của Bbum . Ngoài ra, sau đó đọc tóm tắt của tôi.


atomic sẽ luôn luôn đảm bảo

  • Nếu hai người khác nhau muốn đọc và viết cùng một lúc, giấy của bạn sẽ không bị cháy! -> Ứng dụng của bạn sẽ không bao giờ bị sập, ngay cả trong điều kiện cuộc đua.
  • Nếu một người đang cố viết và chỉ viết 4 trong số 8 chữ cái để viết, thì không thể đọc ở giữa, việc đọc chỉ có thể được thực hiện khi tất cả 8 chữ cái được viết -> Không đọc (nhận) sẽ xảy ra 'một chủ đề vẫn đang viết', tức là nếu có 8 byte đến byte được viết và chỉ có 4 byte được viết thì thôi, thời điểm đó, bạn không được phép đọc từ đó. Nhưng kể từ khi tôi biết nó sẽ không sụp đổ sau đó nó sẽ đọc từ giá trị của một autoreleased đối tượng.
  • Nếu trước khi viết bạn đã xóa những gì trước đây được viết trên giấy và sau đó ai đó muốn đọc bạn vẫn có thể đọc. Làm sao? Bạn sẽ đọc từ một cái gì đó tương tự như Thùng rác Mac OS (vì Thùng rác vẫn chưa bị xóa 100% ... nó ở trong tình trạng lấp lửng) ---> Nếu ThreadA sẽ đọc trong khi ThreadB đã được xử lý để viết, bạn sẽ nhận được một giá trị từ giá trị được viết hoàn toàn cuối cùng bởi ThreadB hoặc nhận được một cái gì đó từ nhóm tự động điền.

Giữ lại số lượng là cách thức quản lý bộ nhớ trong Objective-C. Khi bạn tạo một đối tượng, nó có số giữ lại là 1. Khi bạn gửi cho đối tượng một thông điệp lưu giữ, số lượng giữ lại của nó được tăng thêm 1. Khi bạn gửi một đối tượng một thông báo phát hành, số lượng giữ lại của nó sẽ giảm đi 1. Khi bạn gửi cho đối tượng một tin nhắn tự động , số lượng giữ lại của nó sẽ giảm đi 1 ở một giai đoạn nào đó trong tương lai. Nếu số lượng giữ lại của một đối tượng bị giảm xuống 0, nó sẽ bị hủy.

  • Nguyên tử không đảm bảo an toàn cho luồng, mặc dù nó hữu ích để đạt được an toàn của luồng. An toàn chủ đề liên quan đến cách bạn viết mã / hàng đợi chủ đề mà bạn đang đọc / viết từ đó. Nó chỉ đảm bảo đa luồng không bị sập.

Gì?! Là đa luồng và an toàn chủ đề khác nhau?

Đúng. Đa luồng có nghĩa là: nhiều luồng có thể đọc cùng một dữ liệu được chia sẻ cùng một lúc và chúng tôi sẽ không gặp sự cố, tuy nhiên điều đó không đảm bảo rằng bạn không đọc từ giá trị không được tự động. Với tính an toàn của luồng, đảm bảo rằng những gì bạn đọc không được tự động phát hành. Lý do mà chúng tôi không biến mọi thứ thành nguyên tử theo mặc định là vì có chi phí hiệu năng và đối với hầu hết mọi thứ không thực sự cần sự an toàn của luồng. Một vài phần trong mã của chúng tôi cần nó và đối với một vài phần đó, chúng tôi cần viết mã theo cách an toàn theo luồng bằng cách sử dụng khóa, mutex hoặc đồng bộ hóa.


nonatomic

  • Vì không có thứ gì như Mac OS Trash Bin, nên không ai quan tâm bạn có luôn nhận được giá trị hay không (<- Điều này có thể dẫn đến sự cố), cũng không ai quan tâm nếu ai đó cố đọc nửa chừng bài viết của bạn (mặc dù Viết nửa chừng trong bộ nhớ rất khác với viết nửa chừng trên giấy, trên bộ nhớ nó có thể mang lại cho bạn một giá trị ngu ngốc điên rồ từ trước, trong khi trên giấy bạn chỉ thấy một nửa những gì đã được viết) -> Không đảm bảo không bị sập, bởi vì Nó không sử dụng cơ chế autorelease.
  • Không đảm bảo giá trị bằng văn bản đầy đủ để được đọc!
  • Nhanh hơn nguyên tử

Nhìn chung, chúng khác nhau ở 2 khía cạnh:

  • Sụp đổ hay không vì có hoặc không có bể tự động.

  • Cho phép được đọc ngay giữa "giá trị chưa viết xong hoặc giá trị trống" hoặc không cho phép và chỉ cho phép đọc khi giá trị được ghi đầy đủ .


9

Nếu bạn đang sử dụng thuộc tính của mình trong mã đa luồng thì bạn sẽ có thể thấy sự khác biệt giữa các thuộc tính nguyên tử và nguyên tử. Nonatomic nhanh hơn nguyên tử và nguyên tử là an toàn chủ đề, không nonatomic.

Vijayendra Tripathi đã đưa ra một ví dụ cho một môi trường đa luồng.


9
  • -Atomic có nghĩa là chỉ có một luồng truy cập vào biến (kiểu tĩnh).
  • -Atomic là chủ đề an toàn.
  • -Nhưng nó là hiệu suất chậm

Cách khai báo:

Vì nguyên tử là mặc định vậy,

@property (retain) NSString *name;

VÀ trong tập tin thực hiện

self.name = @"sourov";

Giả sử một nhiệm vụ liên quan đến ba thuộc tính là

 @property (retain) NSString *name;
 @property (retain) NSString *A;
 @property (retain) NSString *B;
 self.name = @"sourov";

Tất cả các thuộc tính hoạt động song song (như không đồng bộ).

Nếu bạn gọi "tên" từ luồng A ,

Đồng thời nếu bạn gọi

[self setName:@"Datta"]

từ luồng B ,

Bây giờ Nếu * tên thuộc tính là nonatomic thì

  • Nó sẽ trả về giá trị "Datta" cho A
  • Nó sẽ trả về giá trị "Datta" cho B

Đó là lý do tại sao phi nguyên tử được gọi là luồng không an toàn Nhưng nó có hiệu suất nhanh vì thực thi song song

Bây giờ Nếu * tên thuộc tính là nguyên tử

  • Nó sẽ đảm bảo giá trị "Sourov" cho A
  • Sau đó, nó sẽ trả về giá trị "Datta" cho B

Đó là lý do tại sao nguyên tử được gọi là chủ đề An toàn đó là lý do tại sao nó được gọi là an toàn đọc-ghi

Tình hình hoạt động như vậy sẽ thực hiện ser seri. Và chậm trong hiệu suất

- Nonatomic có nghĩa là nhiều luồng truy cập biến (loại động).

- Nonatomic là chủ đề không an toàn.

- nhưng nó có hiệu suất nhanh

-Nonatomic KHÔNG phải là hành vi mặc định, chúng ta cần thêm từ khóa nonatomic trong thuộc tính property.

For In Swift Xác nhận rằng các thuộc tính Swift là không tương tự theo nghĩa ObjC. Một lý do là bạn nghĩ về việc liệu tính nguyên tử trên mỗi tài sản có đủ cho nhu cầu của bạn hay không.

Tham khảo: https://forums.developer.apple.com/thread/25642

Để biết thêm thông tin, vui lòng truy cập trang web http://rdcworld-iphone.blogspot.in/2012/12/variable-property-attribut-or.html


4
Như nhiều nhiều nhiều người khác maaaaany đã nói, atomicKHÔNG thread-safe! Nó có khả năng chống lại các vấn đề luồng hơn, nhưng không an toàn cho luồng. Nó chỉ đảm bảo bạn sẽ nhận được toàn bộ giá trị, còn gọi là giá trị "chính xác" (mức nhị phân), nhưng không có nghĩa là nó sẽ đảm bảo rằng đó là giá trị hiện tại và "chính xác" cho logic kinh doanh của bạn (nó có thể là giá trị trong quá khứ và không hợp lệ bởi logic của bạn).
Alejandro Iván

6

Nguyên tử nguyên tử (mặc định)

Nguyên tử là mặc định: nếu bạn không gõ bất cứ thứ gì, tài sản của bạn là nguyên tử. Một thuộc tính nguyên tử được đảm bảo rằng nếu bạn cố đọc từ nó, bạn sẽ nhận lại giá trị hợp lệ. Nó không đảm bảo về giá trị đó có thể là gì, nhưng bạn sẽ lấy lại được dữ liệu tốt, không chỉ bộ nhớ rác. Điều này cho phép bạn làm là nếu bạn có nhiều luồng hoặc nhiều tiến trình trỏ đến một biến duy nhất, một luồng có thể đọc và luồng khác có thể viết. Nếu chúng đánh cùng một lúc, luồng người đọc được đảm bảo có được một trong hai giá trị: trước khi thay đổi hoặc sau khi thay đổi. Những gì nguyên tử không cung cấp cho bạn là bất kỳ loại đảm bảo nào về những giá trị nào bạn có thể nhận được. Nguyên tử thực sự thường bị nhầm lẫn với an toàn luồng, và điều đó không chính xác. Bạn cần đảm bảo an toàn chủ đề của bạn theo những cách khác.

nonatomic

Mặt khác, phi nguyên tử, như bạn có thể đoán, chỉ có nghĩa là, đừng làm điều đó nguyên tử. Những gì bạn mất là đảm bảo rằng bạn luôn nhận lại được một cái gì đó. Nếu bạn cố đọc ở giữa một bản ghi, bạn có thể lấy lại dữ liệu rác. Nhưng mặt khác, bạn đi nhanh hơn một chút. Bởi vì các thuộc tính nguyên tử phải thực hiện một số phép thuật để đảm bảo rằng bạn sẽ lấy lại giá trị, chúng chậm hơn một chút. Nếu đó là một tài sản mà bạn đang truy cập rất nhiều, bạn có thể muốn thả xuống không phổ biến để đảm bảo rằng bạn không phải chịu hình phạt tốc độ đó. Truy cập

lịch sự https://academy.realm.io/posts/tmi-objective-c-property-attribut/

Các thuộc tính nguyên tử (nguyên tử và không nguyên tử) không được phản ánh trong khai báo thuộc tính Swift tương ứng, nhưng các đảm bảo tính nguyên tử của việc triển khai Objective-C vẫn được giữ khi thuộc tính được nhập từ Swift.

Vì vậy - nếu bạn xác định một thuộc tính nguyên tử trong Objective-C, nó sẽ vẫn là nguyên tử khi được Swift sử dụng.

lịch sự https://medium.com/@YogevSitton/atomic-vs-non-atomic-properies-crash-cference-d11c23f4366c


5

Thuộc tính nguyên tử đảm bảo giữ lại một giá trị khởi tạo hoàn toàn bất kể có bao nhiêu luồng đang thực hiện getter & setter trên nó.

Thuộc tính nonatomic chỉ định rằng các bộ truy cập được tổng hợp chỉ đơn giản đặt hoặc trả về một giá trị trực tiếp, không đảm bảo về những gì xảy ra nếu cùng một giá trị được truy cập từ các luồng khác nhau.


3

Nguyên tử có nghĩa là chỉ một luồng có thể truy cập vào biến tại một thời điểm (kiểu tĩnh). Nguyên tử là an toàn chủ đề, nhưng nó là chậm.

Nonatomic có nghĩa là nhiều luồng có thể truy cập vào biến cùng một lúc (kiểu động). Nonatomic là chủ đề không an toàn, nhưng nó là nhanh chóng.


1

Nếu bạn đang sử dụng nguyên tử, điều đó có nghĩa là luồng sẽ an toàn và chỉ đọc. Nếu bạn đang sử dụng nonatomic, điều đó có nghĩa là nhiều luồng truy cập vào biến và luồng không an toàn, nhưng nó được thực thi nhanh, thực hiện thao tác đọc và ghi; đây là một loại năng động.


1

Sự thật là họ sử dụng khóa quay để thực hiện tài sản nguyên tử. Mã như sau:

 static inline void reallySetProperty(id self, SEL _cmd, id newValue, 
      ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 
    {
        id oldValue;
        id *slot = (id*) ((char*)self + offset);

        if (copy) {
            newValue = [newValue copyWithZone:NULL];
        } else if (mutableCopy) {
            newValue = [newValue mutableCopyWithZone:NULL];
        } else {
            if (*slot == newValue) return;
            newValue = objc_retain(newValue);
        }

        if (!atomic) {
            oldValue = *slot;
            *slot = newValue;
        } else {
            spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
            _spin_lock(slotlock);
            oldValue = *slot;
            *slot = newValue;        
            _spin_unlock(slotlock);
        }

        objc_release(oldValue);
    }

0

Để đơn giản hóa toàn bộ sự nhầm lẫn, hãy cho chúng tôi hiểu khóa mutex.

Khóa Mutex, theo tên, khóa khả năng biến đổi của đối tượng. Vì vậy, nếu đối tượng được truy cập bởi một lớp, không có lớp nào khác có thể truy cập cùng một đối tượng.

Trong iOS, @sychronisecũng cung cấp khóa mutex. Bây giờ, nó phục vụ trong chế độ FIFO và đảm bảo luồng không bị ảnh hưởng bởi hai lớp chia sẻ cùng một thể hiện. Tuy nhiên, nếu tác vụ nằm trên luồng chính, tránh truy cập đối tượng bằng các thuộc tính nguyên tử vì nó có thể giữ UI của bạn và làm giảm hiệu suất.


-1

Nguyên tử: Đảm bảo an toàn cho luồng bằng cách khóa luồng bằng NSLOCK.

Không nguyên tử: Không đảm bảo an toàn cho luồng vì không có cơ chế khóa luồng.


-1

Thuộc tính nguyên tử : - Khi một biến được gán với thuộc tính nguyên tử có nghĩa là nó chỉ có một quyền truy cập luồng và nó sẽ là luồng an toàn và sẽ tốt trong phối cảnh hiệu suất, sẽ có hành vi mặc định.

Thuộc tính phi nguyên tử : - Khi một biến được gán với thuộc tính nguyên tử có nghĩa là nó có quyền truy cập đa luồng và nó sẽ không an toàn cho luồng và sẽ chậm trong phối cảnh hiệu suất, sẽ có hành vi mặc định và khi hai luồng khác nhau muốn truy cập biến cùng một lúc Nó sẽ cho kết quả bất ngờ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.