Có phải trình biên dịch của tôi bỏ qua thành viên lớp thread_local tĩnh không sử dụng của tôi?


10

Tôi muốn thực hiện một số đăng ký chủ đề trong lớp của mình, vì vậy tôi quyết định thêm một kiểm tra cho thread_localtính năng:

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

Mã rất đơn giản. BarLớp tôi có một thread_localthành viên tĩnh foo. Nếu một tĩnh thread_local Foo foođược tạo, nó có nghĩa là một luồng được tạo.

Nhưng khi tôi chạy mã, không có gì trong Foo()bản in và nếu tôi xóa nhận xét trong hàm Bartạo, sử dụng foo, mã sẽ hoạt động tốt.

Tôi đã thử điều này trên GCC (7.4.0) và Clang (6.0.0) và kết quả là như nhau. Tôi đoán rằng trình biên dịch phát hiện ra rằng fookhông được sử dụng và không tạo ra một thể hiện. Vì thế

  1. Trình biên dịch đã bỏ qua các static thread_localthành viên? Làm thế nào tôi có thể gỡ lỗi cho điều này?
  2. Nếu vậy, tại sao một staticthành viên bình thường không có vấn đề này?

Câu trả lời:


9

Không có vấn đề với quan sát của bạn. [basic.stc.static] / 2 cấm loại bỏ các biến có thời lượng lưu trữ tĩnh:

Nếu một biến có thời lượng lưu trữ tĩnh có khởi tạo hoặc hàm hủy có tác dụng phụ, thì nó sẽ không bị loại bỏ ngay cả khi nó dường như không được sử dụng, ngoại trừ một đối tượng lớp hoặc bản sao / di chuyển của nó có thể bị loại bỏ như được chỉ định trong [class.copy] .

Hạn chế này không có trong thời hạn lưu trữ khác. Trên thực tế, [basic.stc.thread] / 2 nói:

Một biến có thời lượng lưu trữ luồng sẽ được khởi tạo trước khi sử dụng lần đầu tiên và, nếu được xây dựng , sẽ bị hủy khi thoát luồng.

Điều này cho thấy rằng một biến có thời gian lưu trữ luồng không cần phải được xây dựng trừ khi sử dụng odr.


Nhưng tại sao có sự khác biệt này?

Đối với thời lượng lưu trữ tĩnh, chỉ có một phiên bản của một biến cho mỗi chương trình. Các tác dụng phụ của việc xây dựng chúng có thể là đáng kể (giống như một nhà xây dựng toàn chương trình), vì vậy các tác dụng phụ là bắt buộc.

Tuy nhiên, đối với thời lượng lưu trữ cục bộ của luồng, có một vấn đề: thuật toán có thể bắt đầu rất nhiều luồng. Đối với hầu hết các chủ đề này, biến là hoàn toàn không liên quan. Sẽ rất vui nếu một thư viện mô phỏng vật lý bên ngoài std::reduce(std::execution::par_unseq, first, last)kết thúc việc tạo ra rất nhiều footrường hợp, phải không?

Tất nhiên, có thể có một cách sử dụng hợp pháp cho các tác dụng phụ của việc xây dựng các biến của thời lượng lưu trữ cục bộ của luồng không được sử dụng (ví dụ: trình theo dõi luồng). Tuy nhiên, lợi thế để đảm bảo điều này là không đủ để bù đắp cho nhược điểm đã nói ở trên, vì vậy các biến này được phép loại bỏ miễn là chúng không được sử dụng. (Tuy nhiên, trình biên dịch của bạn có thể chọn không làm. Và bạn cũng có thể tạo trình bao bọc của riêng mình xung quanh std::threadđể đảm nhiệm việc này.)


1
Được rồi ... nếu tiêu chuẩn đã nói như vậy, thì đúng hơn ... Nhưng không có gì lạ khi ủy ban không coi các tác dụng phụ là thời lượng lưu trữ tĩnh?
ravenisadesk

@reavenisadesk Xem câu trả lời cập nhật.
LF

1

Tôi đã tìm thấy thông tin này trong " Xử lý ELF cho lưu trữ cục bộ " có thể chứng minh câu trả lời của @LF

Ngoài ra, hỗ trợ thời gian chạy nên tránh tạo bộ lưu trữ cục bộ luồng nếu không cần thiết. Ví dụ, một mô-đun được tải chỉ có thể được sử dụng bởi một luồng của nhiều luồng tạo nên quá trình. Sẽ là lãng phí bộ nhớ và thời gian để phân bổ dung lượng lưu trữ cho tất cả các luồng. Một phương pháp lười biếng là muốn. Đây không phải là gánh nặng thêm vì yêu cầu xử lý các đối tượng được tải động đã yêu cầu nhận ra bộ nhớ chưa được phân bổ. Đây là cách duy nhất để dừng tất cả các luồng và phân bổ dung lượng cho tất cả các luồng trước khi cho phép chúng chạy lại.

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.