2009-05-18 29 views
29

Nếu tôi có chương trình đa luồng đọc bộ nhớ loại bộ nhớ cache theo tham chiếu. Tôi có thể thay đổi con trỏ này theo chủ đề chính mà không có rủi ro về bất kỳ chủ đề nào khác đọc các giá trị không mong muốn.Việc thay đổi một con trỏ được coi là một hành động nguyên tử trong C?

Như tôi thấy, nếu thay đổi là nguyên tử, các chủ đề khác sẽ đọc giá trị cũ hơn hoặc giá trị mới hơn; không bao giờ bộ nhớ ngẫu nhiên (hoặc con trỏ null), phải không?

Tôi biết rằng có lẽ tôi nên sử dụng phương thức đồng bộ hóa, nhưng tôi vẫn tò mò.

Con trỏ có thay đổi nguyên tử không?

Cập nhật: nền tảng của tôi là 64-bit Linux (2.6.29), mặc dù tôi muốn một câu trả lời cross-platform cũng :)

Trả lời

23

Như những người khác đã đề cập, không có gì trong ngôn ngữ C đảm bảo điều này, và nó phụ thuộc vào nền tảng của bạn.

Trên hầu hết các nền tảng máy tính để bàn hiện đại, việc đọc/ghi vào vị trí được căn chỉnh theo kích thước từ sẽ là nguyên tử. Nhưng điều đó thực sự không giải quyết được vấn đề của bạn, do bộ xử lý và trình biên dịch sắp xếp lại các lần đọc và viết.

Ví dụ, đoạn mã sau được chia:

Chủ đề A:

DoWork(); 
workDone = 1; 

Thread B:

while(workDone != 0); 

ReceiveResultsOfWork(); 

Mặc dù ghi vào workDone là nguyên tử, trên nhiều hệ thống có không có sự bảo đảm nào của bộ vi xử lý mà ghi vào workDone sẽ hiển thị cho các bộ vi xử lý khác trước khi ghi xong thông qua DoWork() là vi sible. Trình biên dịch cũng có thể được tự do sắp xếp lại bản ghi thành workDone trước cuộc gọi đến DoWork(). Trong cả hai trường hợp, ReceiveResultsOfWork() có thể bắt đầu hoạt động trên dữ liệu không đầy đủ.

Tùy thuộc vào nền tảng của bạn, bạn có thể cần chèn hàng rào bộ nhớ, v.v. để đảm bảo đặt hàng phù hợp. Điều này có thể rất khó khăn để có được quyền.

Hoặc chỉ sử dụng khóa. Đơn giản hơn nhiều, dễ dàng hơn nhiều để xác minh là chính xác và trong hầu hết các trường hợp, hiệu suất cao hơn nhiều.

+0

Thực ra, tôi không nghĩ rằng trình biên dịch C (hoặc C++) có thể sắp xếp lại lệnh workDone = 1 thành TRƯỚC KHI một cuộc gọi hàm trước nó, trừ khi nó biết cái gì khác về workDone (như từ khóa "noalias" không được chấp nhận). Mặt khác, nếu bạn có foo = bar; qwerty = uiop; workDone = 1, sau đó workDone = 1 có thể di chuyển (hoặc thậm chí tạm thời là giá trị chỉ đăng ký, nếu workDone không biến động). Nếu ReceiveResultsOfWork nhìn vào qwerty hoặc foo, nó có thể không nhận được uiop hoặc bar, mặc dù workDone là 1. Tôi phải kiểm tra xem điều gì sẽ xảy ra nếu DoWork() kết thúc bằng nội tuyến. – jesup

+3

Trình biên dịch có thể thực hiện việc sắp xếp lại nếu nó có thể chứng minh rằng DoWork không truy cập workDone theo bất kỳ cách nào được xác định. Điều này thực sự có thể xảy ra nếu DoWork đủ nhỏ và trong cùng một đơn vị dịch và trình biên dịch quyết định nội tuyến nó. – derobert

+0

Điều quan trọng là trình biên dịch không bị ràng buộc bởi tiêu chuẩn để đảm bảo workDone được ghi vào sau cuộc gọi đến DoWork(). Trong thực tế, bạn chắc chắn là đúng - nếu DoWork() là một hàm thực sự gọi trình biên dịch prbably sẽ không tái sắp xếp. Nếu DoWork được inlined, workDone có thể dễ dàng được sắp xếp lại trước DoWork() hoặc trong nó. – Michael

12

ngôn ngữ C nói gì về việc liệu bất kỳ hoạt động là nguyên tử. Tôi đã làm việc trên vi điều khiển với 8 bit bus và 16-bit con trỏ; bất kỳ thao tác con trỏ nào trên các hệ thống này đều có khả năng không nguyên tử. Tôi nghĩ rằng tôi nhớ Intel 386s (một số trong đó có xe buýt 16-bit) nâng cao mối quan tâm tương tự. Tương tự như vậy, tôi có thể tưởng tượng các hệ thống có CPU 64 bit, nhưng các bus dữ liệu 32 bit, có thể dẫn đến những mối quan tâm tương tự về các hoạt động con trỏ không nguyên tử. (Tôi chưa kiểm tra xem liệu có bất kỳ hệ thống nào thực sự tồn tại không.)

EDIT: Michael's answer cũng đáng đọc. Kích thước xe buýt so với kích thước con trỏ là khó có thể xem xét duy nhất liên quan đến nguyên tử; nó chỉ đơn giản là counterexample đầu tiên mà tôi nghĩ đến.

+1

để con trỏ có thể được tải một nửa tại một công tắc ngữ cảnh? – ojblass

+1

@ojblass: Những điều như vậy đã xảy ra trong các kiến ​​trúc cũ; chúng có thể xảy ra ngay cả với những cái mới hơn. (Xem bình luận cập nhật của tôi.) –

+1

Bạn học điều gì đó mỗi ngày ... cảm ơn ... – ojblass

5

Bạn không đề cập đến nền tảng. Vì vậy, tôi nghĩ rằng một câu hỏi chính xác hơn một chút sẽ là

Thay đổi con trỏ có được đảm bảo là nguyên tử không?

Sự khác biệt là cần thiết vì các triển khai C/C++ khác nhau có thể khác nhau trong hành vi này. Có thể cho một nền tảng cụ thể để đảm bảo nhiệm vụ nguyên tử và vẫn nằm trong tiêu chuẩn.

Để biết liệu điều này có được đảm bảo chung trong C/C++ hay không, câu trả lời là Không. Chuẩn C không đảm bảo như vậy. Cách duy nhất để đảm bảo việc gán con trỏ là nguyên tử là sử dụng một cơ chế cụ thể nền tảng để đảm bảo tính nguyên tử của phép gán. Ví dụ, các phương thức được khóa trong Win32 sẽ cung cấp sự bảo đảm này.

Bạn đang làm việc trên nền tảng nào?

4

Câu trả lời đồng ý là thông số C không yêu cầu gán con trỏ là nguyên tử, do đó bạn không thể dựa vào đó là nguyên tử.

Câu trả lời thực tế có thể là nó có thể phụ thuộc vào nền tảng, trình biên dịch và có thể là sự liên kết của các ngôi sao vào ngày bạn viết chương trình.

+2

Đó không phải là cảnh sát; đó là câu trả lời đúng. Nếu bạn đang sử dụng đồng thời, bạn * cần * để có nó đúng; nếu không bạn lãng phí nhiều thời gian hơn để cố gắng tái tạo một con bọ hung trong một bazillion. –

1

Điều duy nhất được đảm bảo bởi tiêu chuẩn là loại sig_atomic_t.

Như bạn đã thấy từ các câu trả lời khác, có khả năng là OK khi nhắm mục tiêu cấu trúc chung x86, nhưng rất nguy hiểm với phần cứng "đặc biệt" hơn.

Nếu bạn thực sự tuyệt vọng để biết, bạn có thể so sánh sizeof (sig_atomic_t) với sizeof (int *) và xem chúng là hệ thống đích của bạn.

1

Hóa ra đó là một câu hỏi khá phức tạp. Tôi đã yêu cầu một số similar question và đọc mọi thứ tôi đã chỉ ra. Tôi đã học được rất nhiều về cách bộ nhớ đệm hoạt động trong các kiến ​​trúc hiện đại và không tìm thấy bất kỳ thứ gì chắc chắn. Như những người khác đã nói, nếu chiều rộng xe buýt nhỏ hơn chiều rộng bit con trỏ, bạn có thể gặp rắc rối. Cụ thể nếu dữ liệu nằm trên ranh giới dòng bộ nhớ cache.

Kiến trúc thận trọng sẽ sử dụng khóa.

3

Sửa đổi con trỏ 'bình thường' không được đảm bảo là nguyên tử.

kiểm tra 'So sánh và hoán đổi' (CAS) và các hoạt động nguyên tử khác, không phải là tiêu chuẩn C, nhưng hầu hết các trình biên dịch đều có quyền truy cập vào các bộ xử lý nguyên thủy. trong trường hợp gcc GNU, có một số built-in functions

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