C++ 类成员在代码的不同部分具有不同的值

C++ 类成员在代码的不同部分具有不同的值,c++,oop,templates,asynchronous,thread-safety,C++,Oop,Templates,Asynchronous,Thread Safety,我正在尝试在我的C++应用程序中实现非阻塞串行通信。一个线程负责进行串行通信,我已经编写了一个ThreadSafeClass来在串行线程和主线程之间交换数据。以下是我的代码的核心: main.cpp #include "serial.hpp" #include "tsqueue.hpp" int main(int argc, char *argv[]) { serial::init(); while (true) { fgets(s);

我正在尝试在我的C++应用程序中实现非阻塞串行通信。一个线程负责进行串行通信,我已经编写了一个
ThreadSafeClass
来在串行线程和主线程之间交换数据。以下是我的代码的核心:

main.cpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
tsqueue.hpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
#包括
#包括
命名空间队列
{
模板
类ThreadSafeQueue
{
私人:
可变std::mutex\u mtx;
std::队列_que;
公众:
线程安全队列();
~ThreadSafeQueue();
无效排队(常数T和项目);
T tryDequeue(const T&defaultValue,bool&done);
无效清除();
bool isEmpty()常量;
};
模板
ThreadSafeQueue::ThreadSafeQueue(){}
模板
ThreadSafeQueue::~ThreadSafeQueue(){clear();}
模板
void tsqueue::ThreadSafeQueue::enqueue(const T和item)
{
标准:锁和防护锁(mtx);
_(项目),;
}
模板
T tsqueue::ThreadSafeQueue::tryDequeue(const T&defaultValue,bool&done)
{
标准:锁和防护锁(mtx);
如果(_que.empty())
{
完成=错误;
返回默认值;
}
其他的
{
T项=_que.front();
_que.pop();
完成=正确;
退货项目;
}
}
}//名称空间队列
和系列声明/定义

serial.hpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
#包括
#包括“tsqueue.hpp”
名称空间序列
{
静态tsqueue::ThreadSafeQueue inQueue;
静态tsqueue::ThreadSafeQueue outQueue;
void init();
无效关机();
}
serial.cpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
#包括
#包括“serial.hpp”
#包括“tsqueue.hpp”
静态标准::线程_线程;
无效运行()
{
while(true)
{
std::string str=serial::outQueue.tryDequeue(emptyStr,dequeued);
如果(退出队列){/*确实发送'str'*/}
如果(终止请求){break;}
//一些睡眠
}
}
void serial::init()
{
串行::inQueue.clear();
串行::outQueue.clear();
_线程=标准::线程(运行);
}
void serial::shutdown()
{
如果(_-thread.joinable()){u-thread.join();}
}
问题是,当串行线程的
run()
serial.cpp中调用
tryDequeue(…)
时,它总是看到空的
outQueue
。然而,while循环仍然在main.cpp中看到
outQueue
,并提供了数据,即使是在以后的时间。我发现使用vscode的调试工具。我是C++新手,但有其他语言的经验。在上面的代码中我做错了什么?
run()
main()
是否看到不同的对象

编译器:g++7.3.0,环境:Linux(Ubuntu 18.04)

编辑:如果我从
inQueue
outQueue
的定义中删除
static
,则链接器会对这两个定义都产生
多定义
错误。尽管我有适当的include-guard。

(在修复了所有非问题并最终发现了实际问题后进行了大量编辑:)

问题是您有两个完全独立的
outQueue
实例:一个在
main.o
中,另一个在
serial.o
(如果您在Windows上,则为
.obj
)。问题是您在标题中将这些声明为
静态
。这将在包含此标题的每个
*.cpp
/对象中生成此标题的单独副本

理想情况下,
outQueue
不是全局变量。假设它应该是一个全局变量,您可以这样修复它:

serial.hpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
名称空间序列
{    
//这是一个全局变量的声明。它是无害的
//到处都包括这个。
extern tsqueue::ThreadSafeQueue inQueue;
extern tsqueue::ThreadSafeQueue outQueue;
}
serial.cpp

#include "serial.hpp"
#include "tsqueue.hpp"

int main(int argc, char *argv[])
{
    serial::init();
    while (true)
    {
        fgets(s);
        serial::outQueue.enqueue(std::string(s));
    }
    serial::shutdown();
    return 0;
}
#include <mutex>
#include <queue>

namespace tsqueue
{

template <typename T>
class ThreadSafeQueue
{
  private:
    mutable std::mutex _mtx;
    std::queue<T> _que;

  public:
    ThreadSafeQueue();
    ~ThreadSafeQueue();
    void enqueue(const T &item);
    T tryDequeue(const T &defaultValue, bool &done);
    void clear();
    bool isEmpty() const;
};

template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}

template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }

template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
    std::lock_guard<std::mutex> lock(_mtx);
    _que.push(item);
}

template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
    std::lock_guard<std::mutex> lock(_mtx);
    if (_que.empty())
    {
        done = false;
        return defaultValue;
    }
    else
    {
        T item = _que.front();
        _que.pop();
        done = true;
        return item;
    }
}

} // namespace tsqueue
#include <string>
#include "tsqueue.hpp"

namespace serial
{

static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;

void init();
void shutdown();

}
#include <string>    
#include "serial.hpp"
#include "tsqueue.hpp"

static std::thread _thread;

void run()
{
    while (true)
    {
        std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
        if (dequeued) { /* Do send 'str' */ }
        if (terminationRequested) { break; }
        // Some sleep
    }
}

void serial::init()
{
    serial::inQueue.clear();
    serial::outQueue.clear();
    _thread = std::thread(run);
}

void serial::shutdown()
{
    if (_thread.joinable()) { _thread.join(); }
}
namespace serial
{    
    // This is a declaration of a global variable. It is harmless 
    // to include this everywhere.
    extern tsqueue::ThreadSafeQueue<std::string> inQueue;
    extern tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
namespace serial
{    
    // This is the actual definition of the variables. 
    // Without this you get unresolved symbols during link time
    // (but no error during compile time). If you have this in 
    // two *.cpp files you will get multiple definition linker 
    // errors (but no error at compile time). This must not be 
    // static because we want all other objects to see this as well.
    tsqueue::ThreadSafeQueue<std::string> inQueue;
    tsqueue::ThreadSafeQueue<std::string> outQueue;        
}
名称空间序列
{    
//这是变量的实际定义。
//如果不这样做,您将在链接时间内获得未解析的符号
//(但编译时没有错误)。如果您在
//两个*.cpp文件您将获得多定义链接器
//错误(但编译时没有错误)。这不能是
//静态,因为我们希望所有其他对象也能看到这一点。
tsqueue::ThreadSafeQueue inQueue;
tsqueue::ThreadSafeQueue outQueue;
}

ThreadSafeQueue
本身在我看来是正常的。

是这样吗?如果是这样,你有一个比赛条件。在工作线程检查是否存在之前,您希望主线程将对象添加到队列中,而不做任何事情来保证这样的顺序。一切都可以按以下顺序进行:工作线程启动,工作线程尝试退出队列(在队列中找不到任何元素),退出,主线程向队列添加元素。不要手动锁定互斥锁,相反,请使用
std::lock\u guard
std::unique\u lock
。建议:这是一个非常好的工具,可以确保在发生错误时解锁互斥锁。其成本与只需调用
mutex.lock()
相同。如果您对这个呼叫没有问题,那么您应该对
lock\u guard.lock()
没有问题。请参阅下面关于如何正确移除静电干扰的答案。您需要在头中使用extern,而在cpp文件中不使用static。那就行了。当然最好避免使用全局变量。@SergeyA:请帮助我:为什么我不能将常量引用传递到
enqueue()
?我一直在考虑使用
enqueue(const std::string&item)
作为模板的具体实例。代码现在可以编辑了。它实际上有一个无限的while循环。@JohannesOvermann,对不起,我的错。我读的是
dequeue
而不是
enqueue
。我有!您有两个完全独立的
outQueue
:一个在main.o中,另一个在serial.o中!这一定是它,并解释了你看到的行为。制作