2012-08-16 64 views
70

Tôi nhầm lẫn với mô tả về thread_local trong C++ 11. Sự hiểu biết của tôi là, mỗi luồng có bản sao duy nhất của các biến cục bộ trong một hàm. Các biến toàn cầu/tĩnh có thể được truy cập bởi tất cả các luồng (có thể truy cập đồng bộ bằng cách sử dụng khóa). Và các biến thread_local có thể hiển thị cho tất cả các luồng nhưng chỉ có thể sửa đổi theo luồng mà chúng được định nghĩa? Nó có đúng không?Thread_local có nghĩa là gì trong C++ 11?

Trả lời

73

Thời lượng lưu trữ cục bộ là một thuật ngữ được sử dụng để chỉ dữ liệu có vẻ là toàn cầu hoặc thời gian lưu trữ tĩnh (từ quan điểm của các hàm sử dụng nó), nhưng trên thực tế, có một bản sao cho mỗi chủ đề.

Nó thêm tự động hiện tại (tồn tại trong một khối/chức năng), tĩnh (tồn tại trong thời gian chương trình) và động (tồn tại trên heap giữa phân bổ và deallocation).

Thứ gì đó là luồng địa phương được đưa vào sự tồn tại khi tạo luồng và được xử lý khi chuỗi dừng.

Một số ví dụ tiếp theo.

Hãy nghĩ về trình tạo số ngẫu nhiên trong đó hạt giống phải được duy trì trên cơ sở theo từng luồng. Sử dụng một seed-local seed có nghĩa là mỗi thread có chuỗi số ngẫu nhiên của riêng nó, độc lập với các luồng khác.

Nếu hạt giống của bạn là biến cục bộ trong hàm ngẫu nhiên, nó sẽ được khởi tạo mỗi lần bạn gọi nó, cho bạn cùng một số mỗi lần. Nếu nó là một toàn cầu, chủ đề sẽ can thiệp vào trình tự của nhau.

Ví dụ khác là một ví dụ như strtok nơi trạng thái mã thông báo được lưu trữ trên cơ sở theo chủ đề cụ thể. Bằng cách đó, một chủ đề duy nhất có thể chắc chắn rằng các luồng khác sẽ không làm hỏng các nỗ lực tokenisation của nó, trong khi vẫn có thể duy trì trạng thái trên nhiều cuộc gọi đến strtok - điều này về cơ bản ám strtok_r (phiên bản an toàn chủ đề) dự phòng.

Cả hai ví dụ này đều cho phép biến cục bộ chỉ tồn tại trong phạm vi chức năng sử dụng nó. Trong mã pre-ren, nó sẽ chỉ đơn giản là một biến thời gian lưu trữ tĩnh trong hàm. Đối với các chủ đề, nó được sửa đổi thành chuỗi thời gian lưu trữ cục bộ.

Một ví dụ khác sẽ là một cái gì đó như errno. Bạn không muốn các chủ đề riêng biệt sửa đổi errno sau khi một trong các cuộc gọi của bạn không thành công nhưng trước khi bạn có thể kiểm tra biến, nhưng bạn chỉ muốn một bản sao cho mỗi chuỗi.

This site có mô tả hợp lý về các thông số thời lượng bộ nhớ khác nhau.

+5

+1 cho đề xuất 'strtok'! –

+3

Sử dụng chuỗi địa phương không giải quyết được sự cố với 'strtok'. 'strtok' bị hỏng ngay cả trong môi trường luồng đơn. –

+4

Xin lỗi, hãy để tôi nói lại điều đó. Nó không giới thiệu bất kỳ vấn đề _new_ nào với strtok :-) – paxdiablo

13

Bộ nhớ cục bộ trong mọi khía cạnh như bộ nhớ tĩnh (= toàn cầu), chỉ mỗi thread có một bản sao riêng biệt của đối tượng. Thời gian sống của đối tượng bắt đầu hoặc khi bắt đầu luồng (đối với biến toàn cầu) hoặc khởi tạo lần đầu (đối với các thống kê khối địa phương) và kết thúc khi chuỗi kết thúc (tức là khi join() được gọi).

Do đó, chỉ các biến cũng có thể được khai báo là static có thể được khai báo là thread_local, tức là biến toàn cầu (chính xác hơn: biến "ở phạm vi không gian tên"), thành viên lớp tĩnh và biến khối tĩnh (trong trường hợp này static là bao hàm).

Như một ví dụ, giả sử bạn có một hồ bơi thread và muốn biết như thế nào khối lượng công việc của bạn đang được cân:

thread_local Counter c; 

void do_work() 
{ 
    c.increment(); 
    // ... 
} 

int main() 
{ 
    std::thread t(do_work); // your thread-pool would go here 
    t.join(); 
} 

này sẽ in thống kê sử dụng chủ đề, ví dụ với việc triển khai như sau:

struct Counter 
{ 
    unsigned int c = 0; 
    void increment() { ++c; } 
    ~Counter() 
    { 
     std::cout << "Thread #" << std::this_thread::id() << " was called " 
        << c << " times" << std::endl; 
    } 
}; 
73

Khi bạn khai báo biến thread_local thì mỗi luồng có bản sao riêng. Khi bạn đề cập đến nó theo tên, thì bản sao được liên kết với luồng hiện tại được sử dụng. ví dụ.

thread_local int i=0; 

void f(int newval){ 
    i=newval; 
} 

void g(){ 
    std::cout<<i; 
} 

void threadfunc(int id){ 
    f(id); 
    ++i; 
    g(); 
} 

int main(){ 
    i=9; 
    std::thread t1(threadfunc,1); 
    std::thread t2(threadfunc,2); 
    std::thread t3(threadfunc,3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 
    std::cout<<i<<std::endl; 
} 

Mã này sẽ xuất "2349", "3249", "4239", "4329", "2439" hoặc "3429", nhưng không bao giờ khác. Mỗi luồng có bản sao riêng của i, được gán cho, tăng dần và sau đó được in. Chủ đề chạy main cũng có bản sao của riêng nó, được gán vào lúc bắt đầu và sau đó được giữ nguyên. Những bản sao này hoàn toàn độc lập và mỗi bản sao có một địa chỉ khác nhau.

Chỉ là tên đặc biệt theo khía cạnh đó --- nếu bạn lấy địa chỉ của biến số thread_local thì bạn chỉ có con trỏ bình thường cho đối tượng bình thường mà bạn có thể tự do chuyển giữa các chuỗi. ví dụ.

thread_local int i=0; 

void thread_func(int*p){ 
    *p=42; 
} 

int main(){ 
    i=9; 
    std::thread t(thread_func,&i); 
    t.join(); 
    std::cout<<i<<std::endl; 
} 

Kể từ khi địa chỉ của i được truyền cho hàm chủ đề, sau đó các bản sao của i thuộc các chủ đề chính có thể được giao cho dù nó là thread_local. Chương trình này do đó sẽ xuất ra "42". Nếu bạn làm điều này, thì bạn cần cẩn thận rằng *p không được truy cập sau khi nó thuộc về đã thoát, nếu không bạn sẽ nhận được một con trỏ lơ lửng và hành vi không xác định giống như bất kỳ trường hợp nào khác.

thread_local biến được khởi tạo "trước khi sử dụng lần đầu", vì vậy nếu chúng không bao giờ được chạm vào bởi một chuỗi nhất định thì chúng không nhất thiết phải được khởi tạo. Điều này cho phép các trình biên dịch tránh xây dựng mọi biến số thread_local trong chương trình cho một chuỗi hoàn toàn độc lập và không chạm vào bất kỳ chuỗi nào trong số đó. ví dụ.

struct my_class{ 
    my_class(){ 
     std::cout<<"hello"; 
    } 
    ~my_class(){ 
     std::cout<<"goodbye"; 
    } 
}; 

void f(){ 
    thread_local my_class; 
} 

void do_nothing(){} 

int main(){ 
    std::thread t1(do_nothing); 
    t1.join(); 
} 

Trong chương trình này có 2 chủ đề: chủ đề chính và chuỗi được tạo thủ công. Không có chuỗi nào gọi là f, vì vậy đối tượng thread_local không bao giờ được sử dụng. Do đó, không xác định liệu trình biên dịch sẽ xây dựng 0, 1 hoặc 2 trường hợp của my_class và đầu ra có thể là "", "hellohellogoodbyegoodbye" hoặc "hellogoodbye".

+0

Tôi nghĩ rằng điều quan trọng cần lưu ý là bản sao thread-local của biến là bản sao biến mới được khởi tạo. Tức là, nếu bạn thêm một lệnh 'g()' vào đầu 'threadFunc', thì đầu ra sẽ là' 0304029' hoặc một số hoán vị khác của các cặp '02',' 03' và '04'. Đó là, mặc dù 9 được gán cho 'i' trước khi các luồng được tạo ra, các luồng sẽ nhận được một bản sao mới được xây dựng của' i' trong đó 'i = 0'. Nếu 'i' được gán với' thread_local int i = random_integer() ', thì mỗi luồng sẽ nhận một số nguyên ngẫu nhiên mới. –