2012-03-02 22 views
8

Tôi đang làm việc với một số mã đa luồng cho một dự án trò chơi, và hơi mệt mỏi khi phân loại thông qua quá trình xuất dữ liệu được tạo bởi hai luồng bằng cách sử dụng cout để gỡ lỗi thư cùng một lúc. Tôi đã làm một số nghiên cứu và nhìn chằm chằm vào một bức tường trong một hoặc hai giờ trước khi đến với "cái gì đó". Đoạn mã sau sử dụng SFML để lưu giữ và phân luồng thời gian. Các mutex SFML chỉ được bao bọc các phần quan trọng trong cửa sổ.Chủ đề kỹ thuật an toàn cout. Tui bỏ lỡ điều gì vậy?

Tiêu đề:

#include <SFML\System.hpp> 
#include <iostream> 

class OutputStreamHack 
{ 
    public: 
    OutputStreamHack(); 
    ~OutputStreamHack(); 

    ostream& outputHijack(ostream &os); 

    private: 
    sf::Clock myRunTime; 
    sf::Mutex myMutex; 
}; 

static OutputStream OUTHACK; 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue); 

Thực hiện:

#include <SFML\System.hpp> 
#include <iostream> 

#include "OutputStreamHack.h" 

using namespace std; 

OutputStreamHack::OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

OutputStreamHack::~OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

ostream& OutputStreamHack::outputHijack(ostream &os) 
{ 

    sf::Lock lock(myMutex); 
    os<<"<"<<myRunTime.GetElapsedTime()<<","<<GetCurrentThreadId()<<"> "<<flush; 
    return os; 
} 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue) 
{ 
    OUTHACK.outputHijack(os); 
    return os; 
} 

Cách sử dụng:

cout<<OUTHACK<<val1<<val2<<val3....<<endl; 

Ok, vì vậy cách làm việc này là thông qua một nhà điều hành chèn quá tải mà áp đặt chủ đề an toàn bằng cách khóa một iterator trong một đối tượng tĩnh, sau đó flushing buffer. Nếu tôi hiểu quy trình một cách chính xác (tôi chủ yếu là một lập trình viên tự dạy), cout xử lý các phần tử của chuỗi chèn của nó từ đầu đến cuối, chuyển một biến ostream xuống chuỗi cho mỗi phần tử được thêm vào luồng. Khi nó đạt đến phần tử OUTHACK, toán tử quá tải được gọi, mutex bị khóa và luồng được flushed.

Tôi đã thêm một số thông tin gỡ lỗi id thời gian/luồng vào đầu ra cho mục đích xác minh. Cho đến nay, thử nghiệm của tôi cho thấy rằng phương pháp này hoạt động. Tôi có một số chủ đề đập cout với nhiều đối số, và tất cả mọi thứ được sắp ra theo thứ tự đúng.

Từ những gì tôi đã đọc trong khi nghiên cứu vấn đề này, thiếu an toàn chủ đề trong cout dường như là một vấn đề khá phổ biến mà mọi người gặp phải trong khi mạo hiểm vào lập trình luồng. Những gì tôi đang cố gắng tìm ra là nếu kỹ thuật tôi đang sử dụng là một giải pháp đơn giản cho vấn đề, hoặc tôi nghĩ rằng tôi thông minh nhưng thiếu cái gì đó quan trọng.

Theo kinh nghiệm của tôi, từ thông minh khi được sử dụng để mô tả lập trình chỉ là một từ mã để giảm đau. Tôi đang ở một cái gì đó ở đây, hoặc chỉ cần đuổi theo những hacks tồi tệ xung quanh trong vòng tròn?

Cảm ơn!

+1

Thực tế là nó hoạt động là may mắn. Chỉ có đầu ra của thời gian và id luồng được bảo vệ bởi mutex. Hoàn toàn có thể lấy một luồng khác lẻn vào giữa 'OUTHACK' và' val1'. –

+1

Cân nhắc việc ghi vào 'ostringstream' trước, sau đó bán nội dung trong một thao tác thành' cout'. 'cout' nói chung sẽ là thread an toàn, nó chỉ là khóa rõ ràng cho mỗi cuộc gọi, và mỗi' << 'hoạt động là một cuộc gọi riêng biệt .... Bằng cách đó, không có khóa thêm trong mã ứng dụng của bạn - mà chỉ có thể giảm song song. –

+0

Liên quan: [Là đồng bộ hóa/thread-safe?] (Http://stackoverflow.com/questions/6374264/is-cout-synchronized-thread-safe/6374525#6374525) – legends2k

Trả lời

19

Không phải là chủ đề an toàn ở đây không phải là cout mỗi lần. Nó gọi hai cuộc gọi chức năng theo thứ tự. std::cout << a << b tương đương với cách gọi operator<<(std::cout, a), theo sau là operator<<(std::cout, b). Việc gọi hai hàm theo thứ tự không đảm bảo rằng chúng sẽ được thực thi theo kiểu nguyên tử.

Như vậy, chỉ có đầu ra của thời gian và id luồng được bảo vệ bởi mutex. Hoàn toàn có thể lấy một sợi chỉ khác nằm giữa việc chèn OUTHACKval1, bởi vì khóa không còn được giữ sau khi đã chèn OUTHACK.

Bạn có thể có operator<< cho số OutputStreamHack trả lại theo giá trị một đối tượng mở khóa trong trình phá hủy. Kể từ khi thời gian sống cho đến khi kết thúc của mỗi biểu thức đầy đủ, mã sẽ giữ khóa "cho đến khi dấu chấm phẩy". Tuy nhiên, vì các bản sao có thể có liên quan, điều này có thể có vấn đề mà không cần một hàm khởi động (hoặc một hàm tạo bản sao tùy chỉnh trong C++ 03, tương tự như 's ').

Một tùy chọn khác là sử dụng chuỗi an toàn hiện có của cout (được đảm bảo bằng ngôn ngữ trong C++ 11, nhưng nhiều triển khai đã được tạo luồng trước). Tạo một đối tượng truyền tất cả mọi thứ vào một thành viên std::stringstream và sau đó viết tất cả cùng một lúc khi nó bị hủy.

class FullExpressionAccumulator { 
public: 
    explicit FullExpressionAccumulator(std::ostream& os) : os(os) {} 
    ~FullExpressionAccumulator() { 
     os << ss.rdbuf() << std::flush; // write the whole shebang in one go 
    } 

    template <typename T> 
    FullExpressionAccumulator& operator<<(T const& t) { 
     ss << t; // accumulate into a non-shared stringstream, no threading issues 
     return *this; 
    } 

private: 
    std::ostream& os; 
    std::stringstream ss; 

    // stringstream is not copyable, so copies are already forbidden 
}; 

// using a temporary instead of returning one from a function avoids any issues with copies 
FullExpressionAccumulator(std::cout) << val1 << val2 << val3; 
+2

Ah, tôi thấy khoảng cách trong suy nghĩ của mình . Tôi đã hoạt động theo giả định rằng hành vi cout là đệ quy hơn là lặp đi lặp lại, và rằng nó đã được làm việc trên một dòng địa phương đã được sáp nhập vào stdout tại điểm nơi OUTHACK đã được xử lý. Kỹ thuật của bạn trông giống như những gì tôi đã bỏ lỡ! Cảm ơn một tấn. – Chris

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