Post

(C++) thread는 context가 필요하다.

전역 함수를 threadMain으로 지정해 실행하는건 그냥 다른 언어 처럼 하면 되는데, 객체의 메서드를 threadMain으로 지정하는건 몇 가지 신경써야 할 부분이 있다. 보통 다른 언어 같으면, Thread 클래스 상속받아서 t.run() 해주면 끝인데, C++은 그렇지 못하다.

구식 방법

원래 클래스 내부의 메서드를 threadMain으로 지정하기 위해서는, threadMain가 될 메서드를 static으로 빼고, static 메서드는 클래스 내부의 일반 멤버에 접근하지 못하기 때문에 일반 메서드의 wrapper처럼 사용하고, 진짜 _threadMain은 일반 메서드로 구성한 다음, 그 일반 메서드를 호출하기 위해 threadMain 메서드에서 void\* 형을 인자로 받은 다음에 클래스로 형변환하고, 그 클래스 포인터를 이용해 _theadMain을 호출하는 번거로운 방법을 사용해야 한다.

bind를 사용하는 방법.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#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
2
3
4
5
6
7
[1.val] = 1
[1.val] = 1
set val = 4
[2.val] = 1
[2.val] = 4

1
2
std::thread no\_bind\_t(&TestCls::threadMain, test\_cls);

사실 굳이 bind 붙여주지 않아도 내부적으로 알아서 해주는 듯. boost::thread의 경우 이렇게 넘기면 bind()로 알아서 감싸서 함수와 인자들이 internal storage로 복사된다고 나와있다. std::thread도 boost에서 상당 부분 참고해서 만들었다고 알고있다. 대충 비슷하게 돌아갈 것 같다.

!!!단, 이렇게 했을 때 안되는 경우가 있음.

boost::thread t(&boost::asio::io\_context::run, &\_io\_context);는 bind하면 되고 이건 안되더라. static_cast하면 이렇게 써도 되긴 되는데, bind는 static_cast안써도 잘 되고.

람다를 사용하는 방법★

bind()는 몇 가지 단점을 가지고 있어 람다를 사용하는게 좋다. 단점 중 하나가 overloaded function에 대해서 사용하면 어떤 함수를 바인드해야할지 알 수 없기 때문에, static\_cast<> 해서 딱 지정해줘야한다는 점이다. 이게 상당히 귀찮다.

bind를 사용했던 코드는 이렇게 바꿀 수 있고,

1
2
3
std::thread lambda\_ref\_thread( [&] { test\_cls.threadMain(); });
std::thread lambda\_copy\_thread([=] () mutable { test\_cls.threadMain(); });

threadMain(int) 함수가 오버로딩되었다면, 그냥 이렇게 써주면 된다. bind보다 훨씬 편하다.

1
2
3
std::thread lambda\_ref\_thread( [&] { test\_cls.threadMain(2); });
std::thread lambda\_copy\_thread([=] () mutable { test\_cls.threadMain(2); });

이런 식으로 Wrapping도 가능하지만… 굳이 이렇게 할 필요 없는 듯. 이렇게도 쓸 수 있긴 한데, 그냥 위처럼 람다로 class member function 하나 지정해서 스레드 생성해서 쓰는게 나을 것 같다. std::thread가 이미 잘 되어 있는데 굳이 wrapping 할 필요가. 그리고 중괄호 내에 직접 thread에서 실행할 코드를 넣을 수 있도록 쓰는 문법이 다른 언어(코틀린이라던가.)에서도 지원되는 문법이라, 람다를 쓰는게 더 나은 측면도 있고.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#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;
}

굳이 이렇게 할 필요 없는 듯.

This post is licensed under CC BY 4.0 by the author.