我试图在Java中使用它们之后弄清楚线程并且我有点困惑。 两个问题:
任何正确方向的产品都将受到高度赞赏。
如何解释此消息?
src/CHandler.h:27:9: error: 'thread' in namespace 'std' does not name a type
std::thread _thread;
^
这是我尝试扩展线程:
src/CHandler.h:17:30: error: expected class-name before '{' token
class CHandler : std::thread {
^
完整,麻烦的标题:
#ifndef __projectm__CHandler__
#define __projectm__CHandler__
#include <set>
#include <vector>
#include <thread>
#include "CListener.h"
class CHandler {
public:
virtual bool subscribe(std::shared_ptr<CListener> aListener);
virtual bool unsubscribe(std::shared_ptr<CListener> aListener);
virtual bool hasSubscriber(std::shared_ptr<CListener> aListener);
virtual ~CHandler() {}
protected:
std::thread _thread;
std::vector<std::weak_ptr<CListener> > _subscribers;
std::set<const CListener *> _subscribersSet;
virtual void run();
};
#endif /* defined(__projectm__CDefaultHandler__) */
编译器版本:
bash-3.1$ g++ --version
g++.exe (GCC) 4.8.1
makefile(一团糟,我知道 - 还在学习这个血腥的东西):
CC=g++
OUTFILE=game
BINDIR=bin
SRCDIR=src
OBJDIR=obj
CFLAGS=
LDFLAGS=-std=c++0x
all: core
# Ядро проекта.
core: $(OBJDIR)/main.o $(OBJDIR)/CGame.o $(OBJDIR)/CHandler.o $(OBJDIR)/CListener.o
$(CC) $(CFLAGS) $(wildcard $(OBJDIR)/*.o) -o $(BINDIR)/$(OUTFILE)
$(OBJDIR)/main.o: $(OBJDIR)
$(CC) $(LDFLAGS) $(SRCDIR)/main.cpp -c -o $(OBJDIR)/main.o
$(OBJDIR)/CGame.o: $(OBJDIR)
$(CC) $(LDFLAGS) $(SRCDIR)/CGame.cpp -c -o $(OBJDIR)/CGame.o
$(OBJDIR)/CHandler.o: $(OBJDIR)
$(CC) $(LDFLAGS) $(SRCDIR)/CHandler.cpp -c -o $(OBJDIR)/CHandler.o
$(OBJDIR)/CListener.o: $(OBJDIR)
$(CC) $(LDFLAGS) $(SRCDIR)/CListener.cpp -c -o $(OBJDIR)/CListener.o
# Создаем директорию для объектов, если ее нет.
$(OBJDIR):
mkdir $(OBJDIR)
main.o: $(SRC)/main.cpp
答案 0 :(得分:8)
建议继承std::thread
:它无论如何都没有virtual
方法。我甚至建议不要使用成分。
std::thread
的主要问题是它一旦构建就会启动一个线程(除非你使用它的默认构造函数)。因此,许多情况都充满了危险:
// BAD: Inheritance
class Derived: std::thread {
public:
Derived(): std::thread(&Derived::go, this), _message("Hello, World!") {}
void go() const { std::cout << _message << std::endl; }
private:
std::string _message;
};
线程可能在构建go
之前执行_message
,导致数据竞争。
// BAD: First Attribute
class FirstAttribute {
public:
FirstAttribute(): _thread(&Derived::go, this), _message("Hello, World!") {}
void go() const { std::cout << _message << std::endl; }
private:
std::thread _thread;
std::string _message;
};
同样的问题,线程可能会在构建go
之前执行_message
,导致数据竞争。
// BAD: Composition
class Safer {
public:
virtual void go() const = 0;
protected:
Safer(): _thread(&Derived::go, this) {}
private:
std::thread _thread;
};
class Derived: Safer {
virtual void go() const { std::cout << "Hello, World!\n"; }
};
同样的问题,线程可能会在构建go
之前执行Derived
,导致数据竞争。
正如您所看到的,无论您是继承还是撰写,都很容易在不知不觉中导致数据竞争。使用std::thread
作为类的最后一个属性将有效...如果您可以确保没有人从此类派生。
因此,对我来说,建议仅使用std::thread
作为局部变量似乎更好。请注意,如果您使用async
工具,则您甚至不必自己管理std::thread
。
答案 1 :(得分:8)
使用std::thread
作为简单的局部变量的一个问题是它不是异常安全的。我会承认,在展示小小的HelloWorlds时,我自己经常犯这个罪。
然而,确切地知道您正在进入的内容是很好的,所以这里是使用std::thread
的异常安全方面的更详细解释:
#include <iostream>
#include <thread>
void f() {}
void g() {throw 1;}
int
main()
{
try
{
std::thread t1{f};
g();
t1.join();
}
catch (...)
{
std::cout << "unexpected exception caught\n";
}
}
在上面的例子中,我有一个“大”程序,“偶尔”抛出异常。通常,我想在异常冒出main
之前捕获并处理异常。然而,作为最后的手段,main
本身就包含在try-catch-all中。在这个例子中,我只是打印出一些非常糟糕的事情并退出。在一个更现实的例子中,您可以为您的客户提供节省工作的机会,或释放内存或磁盘空间,启动提交错误报告的其他流程等。
看起来不错吧?不幸错了。运行此时,输出为:
libc++abi.dylib: terminating
Abort trap: 6
在通常从main
返回之前,我没有向我的客户发出错误通知。我期待这个输出:
unexpected exception caught
取而代之的是std::terminate()
。
为什么?
事实证明,~thread()
看起来像这样:
thread::~thread()
{
if (joinable())
terminate();
}
因此,当g()
抛出时,t1.~thread()
在堆栈展开期间运行,并且没有t1.join()
被调用。因此,t1.~thread()
会调用std::terminate()
。
不要问我为什么。这是一个漫长的故事,我缺乏客观性来无偏见地说出来。
无论如何,你必须了解这种行为,并防范它。
一种可能的解决方案是回到包装器设计,可能使用OP首先提出的私有继承,并在其他答案中警告:
class CHandler
: private std::thread
{
public:
using std::thread::thread;
CHandler() = default;
CHandler(CHandler&&) = default;
CHandler& operator=(CHandler&&) = default;
~CHandler()
{
if (joinable())
join(); // or detach() if you prefer
}
CHandler(std::thread t) : std::thread(std::move(t)) {}
using std::thread::join;
using std::thread::detach;
using std::thread::joinable;
using std::thread::get_id;
using std::thread::hardware_concurrency;
void swap(CHandler& x) {std::thread::swap(x);}
};
inline void swap(CHandler& x, CHandler& y) {x.swap(y);}
目的是创建一个新类型,比如说CHandler
,其行为就像std::thread
一样,但它的析构函数除外。 ~CHandler()
应在其析构函数中调用join()
或detach()
。我在上面选择了join()
。现在,我可以在示例代码中将CHandler
替换为std::thread
:
int
main()
{
try
{
CHandler t1{f};
g();
t1.join();
}
catch (...)
{
std::cout << "unexpected exception caught\n";
}
}
现在输出:
unexpected exception caught
按预期。
为什么在join()
中选择detach()
而不是~CHandler()
?
如果使用join()
,则主线程的堆栈展开将阻塞,直到f()
完成。这可能是你想要的,也可能不是。我不能帮你回答这个问题。只有您可以为您的应用程序决定此设计问题。考虑:
// simulate a long running thread
void f() {std::this_thread::sleep_for(std::chrono::minutes(10));}
main()
线程仍然会在g()
下引发异常,但现在它会在展开期间挂起,并且仅在10分钟后打印出来:
unexpected exception caught
然后退出。也许是因为f()
中使用的引用或资源,这就是您需要实现的目标。但如果不是,那么你可以改为:
~CHandler()
{
if (joinable())
detach();
}
然后你的程序将立即输出“意外异常被捕获”并返回,即使f()
仍在忙着匆匆离去(main()
返回f()
之后将被强制取消正常关闭应用程序)。
对于某些线程,您可能需要join()-on-unwinding
,而对其他线程则需要detach()-on-unwinding
。也许这会引导您使用两个CHandler
- 类似包装器,或者基于策略的包装器。委员会无法就解决方案形成共识,因此您必须决定什么是适合您的,或者与terminate()
一起生活。
这直接使用std::thread
非常非常低级别的行为。确定Hello World,但在实际应用程序中,最好通过私有继承或作为私有数据成员封装在中级处理程序中。好消息是,在C ++ 11中,中级处理程序现在可以移植(在std::thread
之上)而不是在C ++中必要时写入操作系统或第三方库98/03
答案 2 :(得分:4)
Bjarne Stroustrup在some examples of using std::thread
中显示C++11 FAQ。最简单的例子如下:
#include<thread>
void f();
struct F {
void operator()();
};
int main()
{
std::thread t1{f}; // f() executes in separate thread
std::thread t2{F()}; // F()() executes in separate thread
}
通常,std::thread
并非旨在从中继承。您传递一个函数以在构造函数中异步执行。
如果您的编译器不支持std::thread
,则可以使用Boost.Thread
。它是fairly compatible,因此一旦您的编译器支持它,您就可以用std::thread
替换它。
答案 3 :(得分:0)
首先,您使用的编译器和编译器版本是什么? std :: thread是相当新的,直到最近才在其中一些中实现。那可能是你的问题。
其次你是
#include <thread>
第三(这不是你的直接问题)不是如何在c ++中使用线程。你没有继承它,你创建了一个传递你想要它运行的函数的实例。
std::thread mythread = std::thread(my_func);
(虽然你可以传递更多的功能)
答案 4 :(得分:0)
确保编译和链接时使用:
g++ -std=c++11 your_file.cpp -o your_program
弄乱LDFLAGS只会帮助链接,而不是编译。