[C++] thread는 context가 필요하다.
전역 함수를 threadMain으로 지정해 실행하는건 그냥 다른 언어 처럼 하면 되는데,
객체의 메서드를 threadMain으로 지정하는건 몇 가지 신경써야 할 부분이 있다.
보통 다른 언어 같으면, `` Thread`` 클래스 상속받아서 ``c t.run()`` 해주면 끝인데, C++은 그렇지 못하다.
구식 방법
원래 클래스 내부의 메서드를 threadMain으로 지정하기 위해서는,
threadMain가 될 메서드를 static으로 빼고,
static 메서드는 클래스 내부의 일반 멤버에 접근하지 못하기 때문에 일반 메서드의 wrapper처럼 사용하고, 진짜 _threadMain은 일반 메서드로 구성한 다음, 그 일반 메서드를 호출하기 위해
threadMain 메서드에서 ``c void*`` 형을 인자로 받은 다음에 클래스로 형변환하고, 그 클래스 포인터를 이용해 _theadMain을 호출하는 번거로운 방법을 사용해야 한다.
bind를 사용하는 방법.
```cpp
#include <functional>
#include <thread>
class TestCls {
public:
int val;
void threadMain(int d) {
printf("[1.val] = %d\n", val);
std::this_thread::sleep_for(std::chrono::seconds(2));
printf("[2.val] = %d\n", val);
}
};
int main()
{
TestCls test_cls;
test_cls.val = 1;
// [Error] std::thread cls_thread(test_cls.threadMain, 3);
/* 안된다. 생각해보면 되면 안된다.
객체는 상태를 가지고 있는데 이런 식으로 넘기면
test_cls.threadMain든, test_cls2.threadMain든 메서드의 주소 정보만 넘어가기 때문에
test_cls의 context에 대한 정보는 넘어가지 않는다.
threadMain라는 함수의 내부에서 클래스의 다른 멤버 변수를 참조한다면
어떤 객체의 멤버 변수를 참조해야할지 알 수 없어진다.
그래서 1. 실행할 메서드 2. 컨텍스트(객체) 3. 인자 이렇게 넘겨줘야 한다. */
std::thread copied_ctx_t(std::bind(&TestCls::threadMain, test_cls));
std::thread refer_ctx_t(std::bind(&TestCls::threadMain, &test_cls));
std::this_thread::sleep_for(std::chrono::seconds(1));
test_cls.val = 4;
printf("set val = 4\n");
/* 이 때, context가 참조로 넘어가는지, 값으로 넘어가는지도 중요하다.
thread에 context를 넘기고 나서 context 객체의 변수 val이 수정될 수 있다.
값으로 넘기게 되면, 객체의 현재 상태를 박제해서 이 context 하에서 threadMain이 실행되고,
참조로 넘기게 되면, thread 코드가 실행되는 context가 변경되었을 때 반영된다. */
copied_ctx_t.join();
refer_ctx_t.join();
return 0;
}
```
```
[1.val] = 1
[1.val] = 1
set val = 4
[2.val] = 1
[2.val] = 4
```
```cpp
std::thread no_bind_t(&TestCls::threadMain, test_cls);
```
사실 굳이 bind 붙여주지 않아도 내부적으로 알아서 해주는 듯.
boost::thread의 경우 이렇게 넘기면 bind()로 알아서 감싸서 함수와 인자들이 internal storage로 복사된다고 나와있다.
std::thread도 boost에서 상당 부분 참고해서 만들었다고 알고있다. 대충 비슷하게 돌아갈 것 같다.
!!!단, 이렇게 했을 때 안되는 경우가 있음.
``cpp boost::thread t(&boost::asio::io_context::run, &_io_context);``는 bind하면 되고 이건 안되더라. static_cast하면 이렇게 써도 되긴 되는데, bind는 static_cast안써도 잘 되고.
람다를 사용하는 방법★
``c bind()``는 몇 가지 단점을 가지고 있어 람다를 사용하는게 좋다.
단점 중 하나가 overloaded function에 대해서 사용하면 어떤 함수를 바인드해야할지 알 수 없기 때문에, ``cpp static_cast<>`` 해서 딱 지정해줘야한다는 점이다. 이게 상당히 귀찮다.
bind를 사용했던 코드는 이렇게 바꿀 수 있고,
```cpp
std::thread lambda_ref_thread( [&] { test_cls.threadMain(); });
std::thread lambda_copy_thread([=] () mutable { test_cls.threadMain(); });
```
``cpp threadMain(int)`` 함수가 오버로딩되었다면, 그냥 이렇게 써주면 된다. bind보다 훨씬 편하다.
```cpp
std::thread lambda_ref_thread( [&] { test_cls.threadMain(2); });
std::thread lambda_copy_thread([=] () mutable { test_cls.threadMain(2); });
```
이렇게도 쓸 수 있긴 한데, 그냥 위처럼 람다로 class member function 하나 지정해서 스레드 생성해서 쓰는게 나을 것 같다.
std::thread가 이미 잘 되어 있는데 굳이 wrapping 할 필요가.
그리고 중괄호 내에 직접 thread에서 실행할 코드를 넣을 수 있도록 쓰는 문법이 다른 언어(코틀린이라던가.)에서도 지원되는 문법이라, 람다를 쓰는게 더 나은 측면도 있고.
```cpp
#include <thread>
#include <atomic>
class UmbumThread {
private:
std::thread worker;
std::atomic<bool> running;
int val;
protected:
void run() {
printf("[1.val] = %d\n", val);
std::this_thread::sleep_for(std::chrono::seconds(1));
printf("[2.val] = %d\n", val);
}
public:
UmbumThread(int _val)
: val(_val), running(false) { }
void start() {
running = true;
worker = std::thread([this]() { this->run(); });
}
void stop() {
// 이를 호출한 (main)thread는 그 부분에서 스레드 종료할 때 까지 blocking됨.
if (worker.joinable()) {
running = false;
worker.join();
}
}
virtual ~UmbumThread() {
stop();
}
};
int main()
{
UmbumThread t(1);
t.start();
std::this_thread::sleep_for(std::chrono::seconds(3));
t.stop();
return 0;
}
```
'Languages & Frameworks > C C++' 카테고리의 다른 글
[C++] 다양한 mutex, lock 중 뭘 써야하나. (0) | 2018.09.13 |
---|---|
[C++] 생성자에서 throw하면(exception) 객체가 없어질까? / thread는 start() 함수로? (0) | 2018.09.12 |
[C++] lambda (bind 보다는 lambda를 쓰자) (0) | 2018.09.03 |
[C++] 컨테이너 안에 클래스가 들어있을 때, 클래스 안의 멤버들을 순회하는 이터레이터를 반환받는 방법 + mem_fn (0) | 2018.09.03 |
[C/C++] strncpy()는 NULL문자를 넣어주지 않는다. (0) | 2018.08.21 |