은성의 Thread 수업

Thread 관련 함수들

  • std::thread
  • std::mutex
  • std::condition_variable
  • std::lock

Thread를 사용하는 목적: 백그라운드에서 함수가 돌아가게 하는 것.

물리적인 thread의 수는 제한되어있지만, OS가 적절하게 스케줄링을 해서 물리적인 thread에 프로세스를 알맞게 붙혀주는 것.

1
2
3
4
5
6
7
8
9
10
11
#include <thread>

void foo1() {};

void foo2() {};

int main()
{
std::thread(foo1);
std::thread(foo2);
}

 


Read / Write

아래 코드를 실행하면 어떤 결과가 나올까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <thread>

void foo1(int a)
{
a = 1;
};

void foo2(int a)
{
a = 2;
};

int main()
{
int a = 0;
std::thread(foo1, a);
std::thread(foo2, a);

a = 3;
std::cout << a << std::endl;
}

정답: a가 뭐가 나올지 알 수 없다 + 터질 수도 있다.

a의 값을 read만 하는거는 터지지는 않는다. 물론 이상한 값이 나올 수 있다.
위 코드는 a를 write하고 있다. Write 중에 접근하면 터질 수 있다.

‘터질 수도 있다’라는 이유는, Write하는 시점을 정하는거는 순전히 OS 종속적이기 때문이다.

 


Unique lock / Mutex

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
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>

void foo1(int a, std::mutex& mutex_for_a)
{
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);
a = 1;
lock_for_a.unlock();
};

void foo2(int a, std::mutex& mutex_for_a)
{
{
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);
a = 2;
lock_for_a.unlock();
};

int main()
{
std::mutex mutex_for_a;
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);

int a = 0;

lock_for_a.unlock();
std::thread thread1(foo1, a);
std::thread thread2(foo2, a);
lock_for_a.lock();

a = 3;
std::cout << a << std::endl;
}

unique_lock을 걸어서 누가 먼저 실행할지 결정할 수 있다.
위의 코드는 a는 무조건 3이 나오는 코드이다. lock을 걸고나서 수행하는 최종 연산이 a=3 대입연산이기 때문이다.
하지만 중간에 foo1이 먼저 실행될지, foo2가 먼저 실행될지는 모른다.
Thread가 생성되는 시간도 고려해야한다.

 

Dual lock

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
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>

void foo1(int a, std::mutex& mutex_for_a)
{
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);
std::unique_lock<std::mutex> lock_for_b(mutex_for_b);

a = 1;
b = 1;
lock_for_a.unlock();
lock_for_b.unlock();
};

void foo2(int a, std::mutex& mutex_for_a)
{
{
std::unique_lock<std::mutex> lock_for_b(mutex_for_b);
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);

a = 2;
b = 2;
lock_for_a.unlock();
lock_for_b.unlock();
};

int main()
{
std::mutex mutex_for_a;
std::mutex mutex_for_b;
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);
std::unique_lock<std::mutex> lock_for_b(mutex_for_b);

int a = 0;
int b = 0;

lock_for_a.unlock();
std::thread thread1(foo1, a);
std::thread thread2(foo2, a);
lock_for_a.lock();

a = 3;
b = 3;
std::cout << a << std::endl;
}

해결 방법:

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
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>

void foo1(int a, std::mutex& mutex_for_a)
{
std::unique_lock<std::mutex> lock_for_a(mutex_for_a);
std::unique_lock<std::mutex> lock_for_b(mutex_for_b);

a = 1;
b = 1;
lock_for_b.unlock();
lock_for_a.unlock();
};

void foo2(int a, std::mutex& mutex_for_a)
{
{
std::unique_lock<std::mutex> lock_for_a(mutex_for_b);
std::unique_lock<std::mutex> lock_for_b(mutex_for_a);

a = 2;
b = 2;
lock_for_b.unlock();
lock_for_a.unlock();
};

int main()
{
std::mutex mutex_for_a;
std::mutex mutex_for_b;
std::unique_lock<std::mutex> lock_for_a(mutex_for_a, std::defer_lock);
std::unique_lock<std::mutex> lock_for_b(mutex_for_b, std::defer_lock);
std::lock_guard<>(lock_for_a, lock_for_b);

int a = 0;
int b = 0;

lock_for_a.unlock();
std::thread thread1(foo1, a);
std::thread thread2(foo2, a);
lock_for_a.lock();

a = 3;
b = 3;
std::cout << a << std::endl;
}