수색…
통사론
- 실()
- 스레드 (스레드 && 기타)
- 명시 적 스레드 (함수 && func, Args && ... args)
매개 변수
매개 변수 | 세부 |
---|---|
other | other 소유권을 가지며, other 는 더 이상 스레드를 소유하지 않습니다. |
func | 별도의 스레드에서 호출하는 함수 |
args | func 대한 인수 |
비고
몇 가지 메모 :
- 두 개의
std::thread
객체는 결코 같은 스레드를 나타낼 수 없습니다 . -
std::thread
객체는 어떤 스레드도 나타내지 않는 상태에있을 수 있습니다 (예 : 이동 후,join
호출 후 등).
스레드 작업
스레드를 시작하면 스레드가 완료 될 때까지 실행됩니다.
종종 어떤 시점에서 예를 들어 결과를 사용하기를 원하기 때문에 스레드가 완료 될 때까지 대기해야합니다 (아마도 스레드가 이미 완료되었을 수 있음).
int n;
std::thread thread{ calculateSomething, std::ref(n) };
//Doing some other stuff
//We need 'n' now!
//Wait for the thread to finish - if it is not already done
thread.join();
//Now 'n' has the result of the calculation done in the seperate thread
std::cout << n << '\n';
또한 스레드를 detach
하여 자유롭게 실행할 수 있습니다.
std::thread thread{ doSomething };
//Detaching the thread, we don't need it anymore (for whatever reason)
thread.detach();
//The thread will terminate when it is done, or when the main thread returns
스레드에 대한 참조 전달
표준 (또는 const
참조)을 std::thread
가 복사 / 이동하기 때문에 스레드에 직접 전달할 수는 없습니다. 대신 std::reference_wrapper
사용 std::reference_wrapper
.
void foo(int& b)
{
b = 10;
}
int a = 1;
std::thread thread{ foo, std::ref(a) }; //'a' is now really passed as reference
thread.join();
std::cout << a << '\n'; //Outputs 10
void bar(const ComplexObject& co)
{
co.doCalculations();
}
ComplexObject object;
std::thread thread{ bar, std::cref(object) }; //'object' is passed as const&
thread.join();
std::cout << object.getResult() << '\n'; //Outputs the result
std :: thread 만들기
C ++에서 스레드는 std :: thread 클래스를 사용하여 생성됩니다. 스레드는 별도의 실행 흐름입니다. 헬퍼가 다른 작업을 수행하는 동안 하나의 작업을 수행하는 것과 유사합니다. 스레드의 모든 코드가 실행되면 종료됩니다 . 쓰레드를 생성 할 때, 쓰레드를 실행시켜야합니다. 스레드에 전달할 수있는 몇 가지 사항은 다음과 같습니다.
- 자유 함수
- 멤버 함수
- 펑터 객체
- 람다 식
자유 함수 예제 - 별도의 스레드에서 함수를 실행합니다 ( 라이브 예제 ).
#include <iostream>
#include <thread>
void foo(int a)
{
std::cout << a << '\n';
}
int main()
{
// Create and execute the thread
std::thread thread(foo, 10); // foo is the function to execute, 10 is the
// argument to pass to it
// Keep going; the thread is executed separately
// Wait for the thread to finish; we stay here until it is done
thread.join();
return 0;
}
멤버 함수 예제 - 별도의 스레드에서 멤버 함수를 실행합니다 ( 라이브 예제 ).
#include <iostream>
#include <thread>
class Bar
{
public:
void foo(int a)
{
std::cout << a << '\n';
}
};
int main()
{
Bar bar;
// Create and execute the thread
std::thread thread(&Bar::foo, &bar, 10); // Pass 10 to member function
// The member function will be executed in a separate thread
// Wait for the thread to finish, this is a blocking operation
thread.join();
return 0;
}
Functor 객체 예제 (실제 예제 ) :
#include <iostream>
#include <thread>
class Bar
{
public:
void operator()(int a)
{
std::cout << a << '\n';
}
};
int main()
{
Bar bar;
// Create and execute the thread
std::thread thread(bar, 10); // Pass 10 to functor object
// The functor object will be executed in a separate thread
// Wait for the thread to finish, this is a blocking operation
thread.join();
return 0;
}
람다 표현식 예제 ( 실례 ) :
#include <iostream>
#include <thread>
int main()
{
auto lambda = [](int a) { std::cout << a << '\n'; };
// Create and execute the thread
std::thread thread(lambda, 10); // Pass 10 to the lambda expression
// The lambda expression will be executed in a separate thread
// Wait for the thread to finish, this is a blocking operation
thread.join();
return 0;
}
현재 스레드의 작업
std::this_thread
는 호출 된 함수에서 현재 스레드에서 흥미로운 것을 수행하는 함수가있는 namespace
입니다.
기능 | 기술 |
---|---|
get_id | thread의 ID를 돌려줍니다. |
sleep_for | 지정된 시간 동안 잠자기 상태가됩니다. |
sleep_until | 특정 시간까지 잠자기 |
yield | 실행중인 스레드를 다시 예약하여 다른 스레드에 우선 순위 부여 |
std::this_thread::get_id
사용하여 현재 스레드 ID std::this_thread::get_id
:
void foo()
{
//Print this threads id
std::cout << std::this_thread::get_id() << '\n';
}
std::thread thread{ foo };
thread.join(); //'threads' id has now been printed, should be something like 12556
foo(); //The id of the main thread is printed, should be something like 2420
std::this_thread::sleep_for
사용하여 3 초 동안 std::this_thread::sleep_for
:
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
}
std::thread thread{ foo };
foo.join();
std::cout << "Waited for 3 seconds!\n";
앞으로 3 시간 동안 std::this_thread::sleep_until
사용하여 std::this_thread::sleep_until
:
void foo()
{
std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::hours(3));
}
std::thread thread{ foo };
thread.join();
std::cout << "We are now located 3 hours after the thread has been called\n";
std::this_thread::yield
사용하여 다른 스레드에 우선 순위를 부여하게 :
void foo(int a)
{
for (int i = 0; i < al ++i)
std::this_thread::yield(); //Now other threads take priority, because this thread
//isn't doing anything important
std::cout << "Hello World!\n";
}
std::thread thread{ foo, 10 };
thread.join();
std :: thread 대신에 std :: async 사용하기
std::async
는 스레드를 만들 수도 있습니다. std::thread
와 비교해 보면 덜 강력하지만 사용하기 쉬운 것으로 간주됩니다. 단지 비동기 적으로 함수를 실행하려고 할 때입니다.
함수를 비동기 적으로 호출
#include <future>
#include <iostream>
unsigned int square(unsigned int i){
return i*i;
}
int main() {
auto f = std::async(std::launch::async, square, 8);
std::cout << "square currently running\n"; //do something while square is running
std::cout << "result is " << f.get() << '\n'; //getting the result from square
}
일반적인 함정
std::async
는 함수에 의해 계산 될 반환 값을 저장하는std::future
를 반환합니다. 그future
가 파괴되면 스레드가 완료 될 때까지 기다려 코드를 효과적으로 단일 스레드로 만듭니다. 반환 값이 필요하지 않을 때 쉽게 간과됩니다.std::async(std::launch::async, square, 5); //thread already completed at this point, because the returning future got destroyed
std::async
는 실행 정책없이 작동하므로std::async(square, 5);
컴파일합니다. 그렇게하면 시스템은 스레드를 만들지 여부를 결정하게됩니다. 시스템이 효율적으로 실행할 수있는 것보다 많은 스레드를 이미 실행하고 있지 않는 한, 시스템이 스레드를 만들기로 결정한 것입니다. 안타깝게도 구현은 일반적으로 그 상황에서 스레드를 생성하지 않기로 선택하므로std::launch::async
해당 동작을 재정 의하여 시스템이 스레드를 생성하도록해야합니다.경쟁 조건을 조심하십시오.
선물과 약속 에 대한 비동기에 대한 추가 정보
스레드가 항상 조인되는지 확인
std::thread
대한 소멸자가 호출되면 join()
또는 detach()
대한 호출이 수행 되어야합니다 . 스레드가 조인되거나 분리되지 않은 경우 기본적으로 std::terminate
가 호출됩니다. RAII를 사용하면 일반적으로 이렇게 간단합니다.
class thread_joiner
{
public:
thread_joiner(std::thread t)
: t_(std::move(t))
{ }
~thread_joiner()
{
if(t_.joinable()) {
t_.join();
}
}
private:
std::thread t_;
}
이것은 다음과 같이 사용됩니다 :
void perform_work()
{
// Perform some work
}
void t()
{
thread_joiner j{std::thread(perform_work)};
// Do some other calculations while thread is running
} // Thread is automatically joined here
이것은 또한 예외 안전을 제공합니다. 우리가 쓰레드를 정상적으로 생성했고 다른 계산을 수행하는 t()
에서 수행 된 작업이 예외를 던지면 join()
은 결코 쓰레드에서 호출되지 않았을 것이고 프로세스는 종료되었을 것입니다.
스레드 객체 재 할당
빈 스레드 객체를 만들어 나중에 작업을 할당 할 수 있습니다.
스레드 오브젝트를 다른 활성, joinable
스레드에 할당하면 스레드가 대체되기 전에 std::terminate
가 자동으로 호출됩니다.
#include <thread>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
}
//create 100 thread objects that do nothing
std::thread executors[100];
// Some code
// I want to create some threads now
for (int i = 0;i < 100;i++)
{
// If this object doesn't have a thread assigned
if (!executors[i].joinable())
executors[i] = std::thread(foo);
}
기본 동기화
스레드 동기화는 다른 동기화 프리미티브 중에서 뮤텍스를 사용하여 수행 할 수 있습니다. 표준 라이브러리에서 제공하는 여러 가지 뮤텍스 유형이 있지만 가장 단순한 것은 std::mutex
입니다. 뮤텍스를 잠 그려면 뮤텍스를 구성하십시오. 가장 간단한 잠금 유형은 std::lock_guard
.
std::mutex m;
void worker() {
std::lock_guard<std::mutex> guard(m); // Acquires a lock on the mutex
// Synchronized code here
} // the mutex is automatically released when guard goes out of scope
std::lock_guard
하면 뮤텍스가 잠금 객체의 전체 수명 동안 잠겨집니다. 영역을 수동으로 잠금 제어해야하는 경우 대신 std::unique_lock
사용하십시오.
std::mutex m;
void worker() {
// by default, constructing a unique_lock from a mutex will lock the mutex
// by passing the std::defer_lock as a second argument, we
// can construct the guard in an unlocked state instead and
// manually lock later.
std::unique_lock<std::mutex> guard(m, std::defer_lock);
// the mutex is not locked yet!
guard.lock();
// critical section
guard.unlock();
// mutex is again released
}
더 많은 스레드 동기화 구조
조건 변수 사용
조건 변수는 스레드 간의 통신을 조정하기 위해 뮤텍스와 함께 사용되는 프리미티브입니다. 이 작업을 수행하는 것이 배타적이거나 가장 효율적인 방법은 아니지만 패턴을 잘 아는 사람들에게는 가장 간단한 방법 일 수 있습니다.
하나는 std::unique_lock<std::mutex>
로 std::condition_variable
을 기다린다. 이를 통해 코드는 획득 진행 여부를 결정하기 전에 공유 상태를 안전하게 검사 할 수 있습니다.
아래는 std::thread
, std::condition_variable
, std::mutex
및 몇 가지 다른 것들을 사용하여 프로듀서 - 소비자 스케치입니다.
#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <queue>
#include <random>
#include <thread>
int main()
{
std::condition_variable cond;
std::mutex mtx;
std::queue<int> intq;
bool stopped = false;
std::thread producer{[&]()
{
// Prepare a random number generator.
// Our producer will simply push random numbers to intq.
//
std::default_random_engine gen{};
std::uniform_int_distribution<int> dist{};
std::size_t count = 4006;
while(count--)
{
// Always lock before changing
// state guarded by a mutex and
// condition_variable (a.k.a. "condvar").
std::lock_guard<std::mutex> L{mtx};
// Push a random int into the queue
intq.push(dist(gen));
// Tell the consumer it has an int
cond.notify_one();
}
// All done.
// Acquire the lock, set the stopped flag,
// then inform the consumer.
std::lock_guard<std::mutex> L{mtx};
std::cout << "Producer is done!" << std::endl;
stopped = true;
cond.notify_one();
}};
std::thread consumer{[&]()
{
do{
std::unique_lock<std::mutex> L{mtx};
cond.wait(L,[&]()
{
// Acquire the lock only if
// we've stopped or the queue
// isn't empty
return stopped || ! intq.empty();
});
// We own the mutex here; pop the queue
// until it empties out.
while( ! intq.empty())
{
const auto val = intq.front();
intq.pop();
std::cout << "Consumer popped: " << val << std::endl;
}
if(stopped){
// producer has signaled a stop
std::cout << "Consumer is done!" << std::endl;
break;
}
}while(true);
}};
consumer.join();
producer.join();
std::cout << "Example Completed!" << std::endl;
return 0;
}
간단한 스레드 풀 만들기
C ++ 11 스레딩 프리미티브는 여전히 상대적으로 낮은 수준입니다. 그것들은 쓰레드 풀처럼 더 높은 수준의 구조체를 작성하는데 사용될 수 있습니다 :
struct tasks {
// the mutex, condition variable and deque form a single
// thread-safe triggered queue of tasks:
std::mutex m;
std::condition_variable v;
// note that a packaged_task<void> can store a packaged_task<R>:
std::deque<std::packaged_task<void()>> work;
// this holds futures representing the worker threads being done:
std::vector<std::future<void>> finished;
// queue( lambda ) will enqueue the lambda into the tasks for the threads
// to use. A future of the type the lambda returns is given to let you get
// the result out.
template<class F, class R=std::result_of_t<F&()>>
std::future<R> queue(F&& f) {
// wrap the function object into a packaged task, splitting
// execution from the return value:
std::packaged_task<R()> p(std::forward<F>(f));
auto r=p.get_future(); // get the return value before we hand off the task
{
std::unique_lock<std::mutex> l(m);
work.emplace_back(std::move(p)); // store the task<R()> as a task<void()>
}
v.notify_one(); // wake a thread to work on the task
return r; // return the future result of the task
}
// start N threads in the thread pool.
void start(std::size_t N=1){
for (std::size_t i = 0; i < N; ++i)
{
// each thread is a std::async running this->thread_task():
finished.push_back(
std::async(
std::launch::async,
[this]{ thread_task(); }
)
);
}
}
// abort() cancels all non-started tasks, and tells every working thread
// stop running, and waits for them to finish up.
void abort() {
cancel_pending();
finish();
}
// cancel_pending() merely cancels all non-started tasks:
void cancel_pending() {
std::unique_lock<std::mutex> l(m);
work.clear();
}
// finish enques a "stop the thread" message for every thread, then waits for them:
void finish() {
{
std::unique_lock<std::mutex> l(m);
for(auto&&unused:finished){
work.push_back({});
}
}
v.notify_all();
finished.clear();
}
~tasks() {
finish();
}
private:
// the work that a worker thread does:
void thread_task() {
while(true){
// pop a task off the queue:
std::packaged_task<void()> f;
{
// usual thread-safe queue code:
std::unique_lock<std::mutex> l(m);
if (work.empty()){
v.wait(l,[&]{return !work.empty();});
}
f = std::move(work.front());
work.pop_front();
}
// if the task is invalid, it means we are asked to abort:
if (!f.valid()) return;
// otherwise, run the task:
f();
}
}
};
tasks.queue( []{ return "hello world"s; } )
는 작업 객체가 실행될 때 hello world
로 채워지는 std::future<std::string>
반환합니다.
tasks.start(10)
(10 개의 스레드를 시작 함 tasks.start(10)
를 실행하여 스레드를 작성합니다.
packaged_task<void()>
은 이동 전용 유형을 저장하는 유형 지우기 std::function
동등성이 없기 때문입니다. 그것들 중 하나를 작성하는 것은 아마도 packaged_task<void()>
사용하는 것보다 빠를 것이다.
실례 예 .
C ++ 11에서는 result_of_t<blah>
를 typename result_of<blah>::type
.
뮤텍스 에 대한 자세한 내용.
스레드 로컬 저장소
스레드 로컬 저장소는 thread_local
키워드를 사용하여 만들 수 있습니다. thread_local
지정자로 선언 된 변수에는 스레드 저장 기간 이 있다고합니다 .
- 프로그램의 각 스레드는 각 스레드 로컬 변수의 자체 복사본을 가지고 있습니다.
- 함수 (로컬) 범위를 가진 스레드 로컬 변수는 처음 컨트롤이 정의를 통과 할 때 초기화됩니다. 이러한 변수는
extern
이라고 선언되지 않는 한 암시 적으로 정적입니다. - 네임 스페이스 또는 클래스 (로컬이 아닌) 범위를 가진 스레드 로컬 변수는 스레드 시작의 일부로 초기화됩니다.
- 스레드 로컬 변수는 스레드 종료시 소멸됩니다.
- 정적 멤버 인 경우 클래스의 멤버는 스레드 로컬 일 수 있습니다. 따라서 (스레드, 인스턴스) 쌍당 하나의 사본이 아니라 스레드 당 하나의 변수 사본이 있습니다.
예:
void debug_counter() {
thread_local int count = 0;
Logger::log("This function has been called %d times by this thread", ++count);
}