semaphore기초
#include <iostream>
#include <mutex>
#include <thread>
#include <string>
#include <chrono>
using namespace std::literals;
std::mutex m;
void Download(std::string name)
{
m.lock();
for(int i = 0; i < 100; i++)
{
std::cout << name;
std::this_thread::sleep_for(30ms);
}
m.unlock();
}
int main()
{
std::thread t1(Download, "1");
std::thread t2(Download, "2");
std::thread t3(Download, "3");
std::thread t4(Download, "4");
std::thread t5(Download, "5");
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
결과를 보면 1이 모두 끝나고 2가 시작 2가 모두 끝나고 3이 시작 … 이런식으로 자원의 독접이 일어난다.
자원을 공유하는 방안은 없을까?
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <semaphore>
using namespace std::literals;
std::counting_semaphore<3> sem(3);
// std::counting_semaphore<MAX_COUNT> sem(초기값);
void Download(std::string name)
{
sem.acquire();
for(int i = 0; i < 100; i++)
{
std::cout << name;
std::this_thread::sleep_for(30ms);
}
sem.release();
}
int main()
{
std::thread t1(Download, "1");
std::thread t2(Download, "2");
std::thread t3(Download, "3");
std::thread t4(Download, "4");
std::thread t5(Download, "5");
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
3개까지는 들어올 수 있다
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <semaphore>
using namespace std::literals;
std::counting_semaphore<3> sem(0); // 초기값이 0이라면?
// 어디선가 release를 해줘야 들어갈 수 있음
std::counting_semaphore<1> sem(1);
// std::binrary_semaphore sem(1); // 위 표현과 동일
// counting_semaphore<1>은 mutex와 동일한거 아닌가 왜만들었지?
// mutex는 lock을 잡은 해당 thread만 unlock이 가능하지만
// counting_semaphore<1>는 어디서든 release가 가능함
thread_local
#include <iostream>
#include <thread>
#include <string_view>
int next3times()
{
// 싱글스레드일때는 정상적으로 동작
static int n = 0;
n = n + 3;
return n;
}
void foo(std::string_view name)
{
std::cout << name << " : " << next3times() << std::endl;
std::cout << name << " : " << next3times() << std::endl;
std::cout << name << " : " << next3times() << std::endl;
}
int main()
{
foo("A");
// 만약 멀티스레드환경이라면?
std::thread t1(foo, "A");
std::thread t2(foo, "\tB");
t1.join();
t2.join();
// 결과부터 말하면 3 6 9 12 15 18 이나오게된다.
// 만약 스레드별로 3 6 9 를 출력하고 싶다면?
// 각각 스레드별로 변수를 갖고싶다(TLS : Thread Local Storage) : 스레드별로 스택을 만들어줘
}
- 예전에는 이렇게 TLS를 사용했다.
- linux(gcc) :
_thread static int x
- windows(cl) :
__declspec(thread)
- C++표준이 나오고 부터 :
thread_local
- linux(gcc) :
int next3times()
{
thread_local int n = 0;
// thread_local static int n = 0;
// static이 암시적으로 선언이 되기에 생략해도 된다.
n = n + 3;
return n;
}
call_once
동일한 함수를 여러 개의 스레드를 수행하지만, 초기화 작업은 한 번만 하고 싶다면?
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std::literals;
std::once_flag init_flag;
void init(int a, double d)
{
std::cout << "init" << std::endl;
std::this_thread::sleep_for(2s);
// 만약 여기서 대기를 해야한다면
// 이 이후에 호출된 스레드들은 모두 std::call_once에서 init이 종료되기를 대기하고 있는다
}
void foo()
{
std::cout << "start foo" << std::endl;
//init(10, 3.4);
std::call_once(init_flag, init, 10, 3.4); // 처음 불린이후에는 호출되지 않음.
std::cout << "finish foo" << std::endl;
}
int main()
{
std::thread t1(foo);
std::thread t2(foo);
std::thread t3(foo);
t1.join();
t2.join();
t3.join();
}
Example
class Singleton
{
private:
Singleton() = default;
static Singleton* sinstance;
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* getInstance()
{
// 여기가 멀티스레드 환경에서 불안정하다
if(sinstance == nullptr)
// new를 하는 중간에 다른 thread에서 new를 해버리면??
sinstance = new Singleton;
return sinstance;
}
};
Singleton* Singleton::sinstance == nullptr;
class Singleton
{
private:
Singleton() = default;
static Singleton* sinstance;
static std::once_flag create_flag;
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* getInstance()
{
std::call_once(create_flag, initSingleton);
return sinstance;
}
static void initSingleton()
{
sinstance = new Singleton;
}
};
Singleton* Singleton::sinstance == nullptr;
std::once_flag Singleton::create_flag;
Example2) Mayer’s Singleton
class Singleton
{
private:
Singleton()
{
std::cout << "start ctor" << std::endl;
std::this_thread::sleep_for(3s);
std::cout << "finish ctor" << std::endl;
}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance()
{
std::cout << "start getInstance" << std::endl;
static Singleton instance;
std::cout << "finish getInstance" << std::endl;
return instance;
// static으로 선언되어있으니 안전할까?
// Yes! C++11부터는 static 지역변수는 스레드에 안전하다
}
};