Extension, refurbishment and re-modeling to this Victorian Terrace including Lower Ground Floor extension, First Floor Extension and Second Floor Loft Conversion. Re-modeled internally throughout.
Los proyectos de software pueden salir bien... pregúnteme comoDaniel Cardelús
Los proyectos de software no acostumbran a salir muy bien. Aquí os explico cual es la manera más fácil de aumentar la tasa de éxito juntando el Design Thinking con la metodología Agile
Millionaires: Do They Still Live Next Door, or Have They Moved?Pamela Danziger
The demographics of millionaires debunk 10 commonly held myths about millionaires that may be impeding marketing to this HNW and UHNW consumer segment defined by wealth, not income.
Introducing GSM-enabled Pay-As-You-Go solar in CambodiaBenoit Lacroix
Since 2014, Kamworks has been introducing solar home systems in Cambodia rural areas with rent-to-own and rental contracts. This innovative business model was enabled by leveraging the collection of repayments using mobile money, and the introduction of machine-to-machine technology to remotely monitor and control the solar systems. This presentation shares the key lessons learned during the development of this project, including customer relationships, technology, partnerships, business model and financing.
2015 - Analisi disoccupazione nella Città di FABRIANORoberto Sorci
Un analisi sulla situazione occupazionale di Fabriano ex capitale degli elettrodomestici che da città della massima occupazione è diventata la città della disoccupazione . Nel documento le motivazioni perché la "politica" chieda per questo territorio il riconoscimento di Area di Crisi Industriale Complessa. Sono illustrate inoltre le motivazioni per cui il territorio fabrianese può essere reindustrializzato con successo .
탑크리에듀(www.topcredu.co.kr), 송석원 교수의 IT기술칼럼#2
자바스크립트는 ES6버전에서 class 키워드를 도입했습니다. 자바스크립트는 객체지향 언어였지만 다른 객체지향언어처럼 사용하기에는 많은 불편함이 있었습니다. 심지어 자바스크립트는 객체지향언어라고 볼 수 없다고 하는 분들도 있었을 정도였죠. 최근에 선풍적인 인기를 얻고 있는 React, Angular 같은 기술은 클래스 문법을 채택했습니다. 따라서, 클래스 문법에 대한 이해가 깊다면 새로운 기술을 습득할 때 많은 도움이 될 것 입니다.
다른 객체지향 언어와 자바스크립트의 차이를 이해하지 못하고 사용하게 되면 많은 문제점이 발생합니다. 안타깝지만 자바스크립트를 사용하고 있는 개발자들 중에서도 제대로 이해하고 사용하시는분은 많지 않은 듯 합니다.
이를 해소하기 위해서 새로 도입한 문법이 class 키워드로 대표되는 클래스 문법입니다. 객체지향 개발방법의 익숙한 개발자들을 위해서 자바스크립트가 새로운 문법을 도입한 것 입니다. 개인적으로 “JavaScript: The Good Parts”의 저자이신 Douglas Crockford 님께서 이를 어떻게 생각하는지 궁금합니다. 자바스크립트를 자바처럼 사용하는 것에 우려를 표하셨었는데, 그 사이 많은 시간이 지났으므로 입장이 바뀌었을지 궁금하군요. 선도적인 개발자들은 ES6에서 도입한 클래스 문법이 좋은 것인가 나쁜 것인가를 갖고 토론을 하기도 합니다.
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard LibraryDongMin Choi
Microsoft Melting Pot
C++ Korea 2nd Seminar
Ranges for The Cpp Standard Library
https://channel9.msdn.com/Events/Channel9-Korea/cplusplus/Ranges-for-The-C-Standard-Library
boost라이브러리 중에서 가장 많이 사용하는 기능인 BOOST_FOREACH()와 shared_ptr의 내부 구조를 분석합니다. 그리고 boost의 내부 구현에 사용된 이 기능을 프로그래밍에 응용하는 방법을 제시합니다.
* BOOST_FOREACH 구조 분석 및 응용
* shared_ptr 구조 분석 및 응용
Similar to Multithread programming 20151206_서진택 (20)
2. 목차 index
문장 statement 과 표현식 expression
rvalue reference
Move semantics
쓰레드 thread
2
3. 문장 statement 과 표현식 expression
문장 statement 과 표현식 expression
rvalue reference
Move semantics
쓰레드 thread
3
4. 문장 statement 과 표현식 expression
Statement 를 구성하는 일부로서 , 값 value 을 가지
면 expression 이라고 합니다 .
Statement 전체가 값 value 을 가진다면 , 그
statement 는 expression 입니다 .
expression 은 항상 statement 입니다 .
– i = i + 4;
– 위 문장에서 expression 은 다음과 같은 것들입니다 .
– i
– i+4
– i=i+4
4
5. 아래의 소스에서 expression 인 문장은 2 개입니다 .
int GetSum( int iLeft_, int iRight_ )
{
return iLeft_ + iRight_;
}//GetSum()
void Test( const int& iData_ )
{
std::cout << __FUNCTION__ << " const reference" << std::endl;
}//Test()
int main()
{
int i = 3;
int j = 0;
j = GetSum( 4, 5 ); // 문장이 값 9 를 가지므로 expression 입니다 .
if( j == 9 )
{
printf( "%irn", j ); // 문장이 값 3 을 가지므로 expression 입니다 .
}//if
Test( i );
}5
6. 표현식의 lvalue/rvalue
int main()
{
int i = 3;
int j = 0;
i = i + 4; //(1)
Test( i );
6
i 의 주소가 [5000] 번지라고 가정합니다 . 32bit 환경에서 j 의 주소는 [4996] 번지가 될 것입니다 .
같은 변수 i 에 대해서 i 가 등호 = 의 왼쪽에 사용되면 [5000] 으로 해석하고 , i 가 등호의 오른쪽에
사용되면 [5000] 번지가 가리키는 값 , 즉 3 으로 해석한다는 것에 주목하세요 .
즉 변수 i 는 사용되는 환경에 따라 expression 에서 평가되는 값 value 이 달라집니다 .
표현식은 값을 가지는데 , 표현식이 등호 = 의 왼쪽에 올 때 가져야 하는 값을 lvalue, 등호의 오른쪽
에 올 때 가져야 하는 값을 rvalue 라고 합니다 .
명심하세요 . lvalue/rvalue 는 문장이나 변수에는 해당하지 않습니다 . lvalue/rvalue 는 표현식
expression 이 가지는 값입니다 .
(1)문장에서 등호의 왼쪽에 있는 표현식 i 는 lvalue 를 가지며 , lvalue 의 값은 [5000] 입니다 . (1)
문장에서 등호의 오른쪽에 있는 표현식 i+4 는 rvalue 를 가지며 , rvalue 의 값은 7 입니다 .
i=i 라는 문장에서 등호의 왼쪽에 있는 표현식 i 는 lvalue 를 가지며 , 값은 [5000] 입니다 . 등호의 오른쪽에
있는 i 는 rvalue 를 가지며 값은 3 입니다 .
7. rvalue 를 함수의 인자로 전달
void Test2( int i, int j )
{
printf( "%p, %prn", &i, &j );
}//Test2()
int main()
{
int i = 3;
int j = 0;
Test2( 5, i );
}//main()
7
표현식의 rvalue 가 값을 인자로 받는 함수로 전달하는 경우 , 호출된 함수 내부에서는 rvalue 성질이 유
지되지 않습니다 .
main() 에서 Test2() 를 호출할 때 , rvalue 5 를 넘겼지만 , Test2() 함수 내부에서는 이 값을 이름
name i 로 참조할 수 있기 때문입니다 .
8. lvalue reference
void Test2( int i, int& j )
{
printf( "%p, %prn", &i, &j );
}//Test2()
int main()
{
int i = 3;
int j = 0;
Test2( i, i );
}//main()
8
Test2() 가 두번째 인자를 참조 lvalue reference 로 받으면 , main() 의 Test2() 호출은 같은 표현식 i
에 대해서 첫번째 i 는 rvalue 3 을 전달하고 , 두번째 i 는 lvalue [5000] 을 전달합니다 .
왜냐하면 Test2( …, int& j) 가 두번째 인자 parameter 의 lvalue reference 를 요구하고 있기 때문입
니다 .
Test2( i, 5 ); 처럼 호출하면 컴파일 시간 에러가 발생합니다 . 왜냐하면 표현식 5 의 lvalue 를 구할
수 없기 때문입니다 .
5+3 이나 i+5 같은 이름을 가지지 않는 표현식은 rvalue 만 가집니다 .
Test2( i, 5 ); // error
9. rvalue 도 어딘가에 할당됨
void Test2( int i, const int& j )
{
printf( "%p, %prn", &i, &j );
}//Test2()
int main()
{
int i = 3;
int j = 0;
Test2( i, 5 );
}//main()
9
하지만 const lvalue reference 를 받으면 , rvalue 를 가지는 표현식을 함수의 인자로 전달하는 것이
가능합니다
이것은 컴파일러가 생성한 코드가 함수로 전달하는 표현식의 값을 , 사용자가 접근할 수 없는 어떤 메
모리 공간에 저장하기 때문에 가능한 일입니다 .
프로그래머는 정상적인 방법으로 [4992] 를 알아낼 방법이 없습니다 .
j == 5, &j==[4984] 이지만 , *j 는 에러입니다 .
컴파일러가 이미 rvalue 의 주소값을 전달하고 있다면 프로그래머가 의도적으로 rvalue 의 주소값을 지속
적으로 전달하도록 할 수 없을까요 ? 그것이 rvalue reference 입니다 .
11. rvalue reference
함수가 rvalue reference 를 받도록 인자를 선언할
수 있습니다 .
타입 이름과 변수 이름 사이에 && 를 사용합니다 .
– int&& iData_
그러면 컴파일러는 인자에 해당하는 표현식이
rvalue 를 가지는 경우 , rvalue 의 주소를 전달하도
록 코드를 생성합니다 .
11
13. rvalue 인자를 다시 rvalue 인자로 전달하기
void Hello( int& iData_ )
{
std::cout << "Hello reference" << std::endl;
}//Hello()
void Hello( int&& iData_ )
{
std::cout << "Hello rvalue reference" << std::endl;
}//Hello()
void Test( int&& iData_ )
{
std::cout << __FUNCTION__ << " rvalue reference" << std::endl;
Hello( iData_ );
}//Test()
13
Test() 는 rvalue reference iData_ 를 받았지만 , Test() 내부에서 iData_ 는 이제 이름으로 참조할
수 있으므로 ( 스택에 할당되고 변수의 주소가 &iData_ 이므로 ), iData_ 라는 변수 표현식은 더 이상
rvalue 가 아닙니다 .
그러므로 Test() 내부의 Hello( iData ); 호출은 lvalue reference 를 인자로 받는 Hello(int& iData_) 를 호출
합니다 .
14. void Hello( int&& iData_ )
{
std::cout << "Hello rvalue reference" << std::endl;
}//Hello()
template<typename T>
typename std::remove_reference<T>::type&& MyMove( T&& t_ )
{
return static_cast<std::remove_reference<T>::type&&>(t_);
}//MyMove()
void Test( int&& iData_ )
{
std::cout << __FUNCTION__ << " rvalue reference" << std::endl;
Hello( MyMove( iData_ ) );
}//Test()
14
rvalue reference 를 받은 함수가 내부에서 호출하는 다른 함수에 인자의 rvalue reference 를 그대로
전달하는 방법은 컴파일러로 하여금 강제로 캐스팅 casting 하도록 하는 것입니다 .
MyMove() 는 인자로 받은 rvalue reference 의 명시적인 rvalue reference 를 리턴합니다 .
그러므로 오버로드된 두 개의 Hello() 중에서 Hello(int&&) 를 호출하는 것이 가능합니다 .
15. std::move
void Hello( int& iData_ )
{
std::cout << "Hello reference" << std::endl;
}//Hello()
void Hello( int&& iData_ )
{
std::cout << "Hello rvalue reference" << std::endl;
}//Hello()
void Test( int&& iData_ )
{
std::cout << __FUNCTION__ << " rvalue reference" << std::endl;
Hello( std::move( iData_ ) );
}//Test()
15
인자로 받은 rvalue reference 의 값을 그대로 유지하는 표준 구현이 std::move() 입니다 .
이렇게 외워 두세요 .
인자로 받은 rvalue reference 를 , 다른 함수에 rvalue reference 로 전달하기 위해서는
std::move() 를 반드시 사용해야 합니다 .
17. Move Semantics
deep copy 가 필요한 클래스는 copy constructor 와
copy assignment operator 를 제공해야 합니다 .
container 가 deep copy 가 필요한 객체들을 노드 node
로 유지할 때 , 노드의 삽입이 일어날 때 , 임시 객체
temporary object 에 대한 copy constructor 와 copy
assignment operator 가 호출됩니다 .
임시 객체는 이름이 없으므로 rvalue 입니다 .
클래스 생성자가 이러한 rvalue 에 대해서 특별하게 동작하
도록 만든다면 , 임시 객체를 위한 복사 동작을 향상 할
수 있습니다 .
이것을 이동 문맥 move semantics 이라고 합니다 .
move semantics 는 move constructor 와 move
assignment operator 를 통해 구현합니다 .
17
18. class MemoryBlock
class MemoryBlock
{
public:
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length);
// Destructor.
~MemoryBlock();
// Copy constructor.
MemoryBlock(const MemoryBlock& other);
// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other);
// Retrieves the length of the data resource.
size_t Length() const;
private:
size_t _length; // The length of the resource.
int* _data; // The resource.
};
18
19. MemoryBlock. 생성자와 파괴자
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length)
: _length(length)
, _data(new int[length])
{
std::cout << "In MemoryBlock(size_t). length = "
<< _length << "." << std::endl;
}
// Destructor.
~MemoryBlock()
{
std::cout << "In ~MemoryBlock(). length = "
<< _length << ".";
if (_data != NULL)
{
std::cout << " Deleting resource.";
// Delete the resource.
delete[] _data;
}
std::cout << std::endl;
}
19
정수를 MemoryBlock 에
할당할 때 , 생성자가 호
출되는 것을 방지하기 위
해 , explicit 이 필요합
니다 .
21. MemoryBlock.Move constructor
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _data(NULL)
, _length(0)
{
std::cout << "In MemoryBlock(MemoryBlock&&). length = "
<< other._length << ". Moving resource." << std::endl;
//*this = std::move( other );
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = NULL;
other._length = 0;
}
21
22. MemoryBlock.Move assignment operator
// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other)
{
std::cout << "In operator=(MemoryBlock&&). length = "
<< other._length << "." << std::endl;
if (this != &other)
{
// Free the existing resource.
if( _data != nullptr )
delete[] _data;
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = NULL;
other._length = 0;
}
return *this;
}
22
24. MemoryBlock.main()
int main()
{
// Create a vector object and add a few elements to it.
std::vector<MemoryBlock> v;
v.push_back(MemoryBlock(25));
v.push_back(MemoryBlock(75));
// Insert a new element into the second position of the vector.
v.insert(v.begin() + 1, MemoryBlock(50));
}
24
25. In Vs2010 or above
In MemoryBlock(size_t). length = 25.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
In operator=(MemoryBlock&&). length = 75.
In operator=(MemoryBlock&&). length = 50.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
25
26. Before Vs2010
In MemoryBlock(size_t). length = 25.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In operator=(const MemoryBlock&). length = 75. Copying resource.
In operator=(const MemoryBlock&). length = 50. Copying resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
26
28. 쓰레드 thread
프로세스 내에서 실행되는 흐름의 단위입니다 .
윈도우즈에서 실행되는 온라인게임의 경우 대부분
WinMain() 에서 하나의 쓰레드가 실행됩니다 .
필요에 따라 다른 쓰레드를 만들고 실행할 수 있습니
다 .
프로세스가 2 개 이상의 쓰레드를 실행하면 멀티쓰레
드 Multithread 프로그램입니다 .
Critical Section
Mutex
Semaphore
TLS(Thread Local Storage)
28
30. 임계영역 Critical Section
두 개의 쓰레드가 같은 루틴을 실행할 수 있습니다 .
그 루틴이 동시에 실행되어서는 안 되는 코드블록이면 그것을 Critical
Section 이라고 합니다 .
Critical Section 의 진입과 탈출을 제어하는 객체를 Mutex 라고 합니다
.30
31. Mutex
Mutual Exclusion 의 약자입니다 .
일반적으로 운영체제가 제공하는 동기화 객체입니다 .
lock() 과 unlock() 을 제공하며 , lock() 과
unlock() 사이의 코드 블록이 Critical Section 입
니다 .
lock() 을 시도한 쓰레드가 unlock() 해야 합니다 .
31
32. Mutex
Mutex 의 Win32 구현이 CRITICAL_SECTION 입니다 .
– EnterCriticalSection()
– LeaveCriticalSection()
– InitializeCriticalSection()
– DeleteCriticalSection()
Mutex 의 표준 구현이 std::mutex 입니다 .
– std::mutex 의 RAII 헬퍼가 std::lock_guard 입니다 .
Mutex 의 boost 구현이 boost::mutex 입니다 .
32
33. 세마포 Semaphore
B 쓰레드가 A 쓰레드의 작업 완료를 기다려야 하는 상황이 있습니다 .
이렇게 여러 쓰레드 사이의 동기화를 제공하는 객체를 Semaphore 라고 합
니다 .
일반적으로 Signal() 과 Wait() 인터페이스를 제공합니다 .
33
35. 데드락 Deadlock
– 어떤 쓰레드도 Critical Section 에 진입하지 못하는 상황입니다 .
– Crash 처럼 프로그램이 종료하지 않습니다 .
– 하지만 아무것도 할 수 없습니다 .
레이스 조건 Race Condition
– 쓰레드가 Critical Section 에 서로 진입하려는 상황입니다 .
굶어죽음 Starvation
– Race 상황에서 특정 쓰레드만 Critical Section 에 진입하지 못하는
상황입니다 .
35
36. 쓰레드 모델
각 쓰레드가 자신이 맡은 고유의 작업을 수행합니다
.
– 대부분의 게임 엔진이 이 모델을 사용합니다 .
– 쓰레드들은 메시지 큐를 통하여 통신합니다 .
임의의 n 개 쓰레드는 다른 작업 Task 를 수행합니다
.
– Task Parallel 하다고 합니다 .
– 이러한 모델은 스케일러블 Scalable 합니다 .
– 즉 Core 의 개수가 늘어나면 쓰레드의 개수를 늘리면 됩니다 .
임의의 n 개 쓰레드는 데이터 영역이 다른 같은 작업
을 수행합니다 .
– Data Parallel 하다고 합니다 .
– 이러한 모델 역시 Scalable 합니다 .
최근의 쓰레드 라이브러리들은 Data Parallelism 과36
37. TLS,Thread Local Storage
어떤 함수가 같은 변수를 접근하는 것 처럼 보이지만 , 쓰레드마다 유일
한 자신의 변수를 접근하도록 변수를 선언할 수 있습니다 .
전역변수처럼 선언하지만 , 쓰레드마다 구별되는 변수입니다 .
이러한 변수를 TLS 라고 합니다 .
TLS 의 Win32 구현이 TlsAlloc() 류의 함수들입니다 .
TLS 의 Microsoft 구현이 __declspec( thread ) 입니다 .
TLS 의 boost 구현이 boost::thread_specific_ptr<> 입니다 .
37
38. 참고 자료
Threading
– http://en.wikibooks.org/wiki/C%2B%2B_Programming/Threading
Thread Support Library
– http://en.cppreference.com/w/cpp/thread
Mutex 와 Semaphore 의 차이
– http://stackoverflow.com/questions/62814/difference-between-
binary-semaphore-and-mutex
std::condition_variable
– http://en.cppreference.com/w/cpp/thread/condition_variable
38
43. #include <thread>
#include <iostream>
void my_thread_func()
{
std::cout<<"hello"<<std::endl;
}
int main()
{
std::thread t(my_thread_func);
t.join();
}
43
std::thread 는 RAII 형식으로만 thread callback 을 실행할 수 있습니다 .
join() 은 쓰레드 t 가 종료하기를 기다립니다 .
44. class bar {
public:
void foo() {
std::cout << "hello from member function" << std::endl;
}
};
int main()
{
bar b;
std::thread t(&bar::foo, &b);
t.join();
}
44
객체의 멤버 함수를 thread callback 으로 전달 할 수 있습니다 .
45. thread.function object 사용하기
#include <thread>
#include <iostream>
class SayHello
{
public:
void operator()() const
{
std::cout<<"hello"<<std::endl;
}
};
int main()
{
std::thread t(SayHello());
t.join();
}
45
46. thread.std::bind 로 함수 객체 만들기
#include <thread>
#include <iostream>
#include <string>
#include <functional>
void greeting(std::string const& message)
{
std::cout<<message<<std::endl;
}
int main()
{
std::thread t(std::bind(greeting,"hi!"));
t.join();
}
46
std::bind 를 이용하여 함수 객체를 리턴하도록 합니다 .
47. thread. 쓰레드 함수로 인자 전달하기
#include <thread>
#include <iostream>
void write_sum(int x,int y)
{
std::cout<<x<<" + "<<y<<" = "<<(x+y)<<std::endl;
}
int main()
{
std::thread t(write_sum,123,456);
t.join();
}
47
std::thread 의 생성자는 가변 인자를 받도록 설계되어 있습니다 . 생성자 구현이 첫번째 파라미터를
쓰레드 함수로 인식하고 , 나머지 값들을 쓰레드 함수의 인자로 가집니다 .
49. thread.shared_ptr 사용하기
#include <>
int main()
{
std::shared_ptr<SayHello> p(new SayHello);
std::thread t(&SayHello::greeting,p,"goodbye");
t.join();
}
49
스마트 포인터를 전달하는 것 가능합니다 . 쓰레드 객체 t 가 살아 있을 동안 , p 의 lifetime 또한 유
지됩니다 .
51. variadic template
int func() {} // termination version
template<typename Arg1, typename... Args>
int func(const Arg1& arg1, const Args&... args)
{
process( arg1 );
func(args...); // note: arg1 does not appear here!
}
51
52. variadic template.specialization
template<typename T>
class Template{
public:
void SampleFunction(T param){
}
};
template<>
class Template<int>{
public:
void SampleFunction(int param){
}
};
52
template<typename... Arguments>
class VariadicTemplate{
public:
void SampleFunction(Arguments... params){
}
};
template<>
class VariadicTemplate<double, int, long>{
public:
void SampleFunction(double param1, int param2
, long param3){
}
};
53. thread.constructor
thread();
(1)(since C++11)thread( thread&& other );
(2)(since C++11)template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
(3)(since C++11)thread(const thread&) = delete ;
(4)(since C++11)Constructs new thread object.
53
표준 constructor 는 variadic template 으로 선언되어 있지만 , Vs2012 의 실제 구현은 BOOST_PP 와
비슷한 구현의 가변 매크로를 사용하여 구현되어 있습니다 .
57. std::unique_lock
std::mutex mtx; // mutex for critical section
void print_block (int n, char c) {
// critical section (exclusive access to std::cout signaled by lifetime of lck):
std::unique_lock<std::mutex> lck (mtx);
for (int i=0; i<n; ++i) { std::cout << c; }
std::cout << 'n';
}
int main ()
{
std::thread th1 (print_block,50,'*');
std::thread th2 (print_block,50,'$');
th1.join();
th2.join();
return 0;
}
57
lock_guard 와 같은 의도로 사용할 수 있습니다 .
출력결과 :
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
58. std::unique_lock<std::mutex> acquire_lock()
{
static std::mutex m;
return std::unique_lock<std::mutex>(m);
}
//std::mutex mtx; // mutex for critical section
void print_block (int n, char c) {
// critical section (exclusive access to std::cout signaled by lifetime of lck):
//std::lock_guard<std::mutex> lck (mtx);
std::unique_lock<std::mutex> lck = acquire_lock();
for (int i=0; i<n; ++i)
{
std::cout << c;
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}
std::cout << 'n';
}
58
move semantics 에 대해 안전하게 동작합니다 . acquire_lock()
이 unique_lock 을 사용하지 않고 lock_guard 를 사용하면
컴파일 타임 에러가 발생합니다 .
59. rvalue 객체 생성 막기
class KPreventRValueObject
{
private:
KPreventRValueObject( KPreventRValueObject&& rvalueref_ );
public:
KPreventRValueObject(){}
private:
std::string m_strData;
};//class KPreventRValueObject
KPreventRValueObject TestRValueObject()
{
return KPreventRValueObject(); // compile time error
}//TestRValueObject()
59
61. public:
data_handle(data_handle&& other)
: ptr(nullptr)
{
*this = std::move( other );
}
data_handle& operator=(data_handle&& other)
{
if( &other != this )
{
ptr = other.ptr;
lk = std::move(other.lk);
other.ptr = 0;
}//if
return *this;
}
void do_op()
{
ptr->some_operation();
}
void do_other_op()
{
ptr->other_operation();
}
};//class data_handle
61
data_handle lock_data()
{
static std::mutex m;
static data_to_protect the_data;
std::unique_lock<std::mutex> lk(m);
return data_handle(&the_data, std::move(lk) );
}//lock_data()
int main()
{
data_handle dh = lock_data(); // lock acquired
dh.do_op(); // lock still held
dh.do_other_op(); // lock still held
data_handle dh2 = std::move(dh); // transfer lock to other
handle
dh2.do_op(); // lock still held
return 0;
}//main()
62. std::unique_lock : 잠시 unlock 하기
std::mutex m;
std::vector<std::string> strings_to_process;
void update_strings()
{
std::unique_lock<std::mutex> lk(m);
if(strings_to_process.empty())
{
lk.unlock();
std::vector<std::string> local_strings=load_strings();
lk.lock();
strings_to_process.insert(strings_to_process.end(),
local_strings.begin(),local_strings.end());
}
}
62
unique_lock 은 RAII 패턴을 사용하면서도 , 원하는 때에
lock/unlock 이 가능합니다 .
63. deadlock 상황의 예
class account
{
std::mutex m;
currency_value balance;
public:
friend void transfer(account& from,account& to,
currency_value amount)
{
std::lock_guard<std::mutex> lock_from(from.m);
std::lock_guard<std::mutex> lock_to(to.m);
from.balance -= amount;
to.balance += amount;
}
};
63
두개의 쓰레드가 accout.transfer( A, B, … ),
account.transfer( B, A, … ) 형태로 호출하면 deaklock
이 발생할 수 있습니다 .
64. struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// don't actually take the locks yet
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
from.num_things -= num;
to.num_things += num;
// 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
64
unique_lock 의 lock 시점을 컨트롤하는 기능을 사용하면
std::lock() 과 사용하여 dead lock 을 예방하는 코드를
작성할 수 있습니다 .
71. Tls 는 하나의 코드 루틴이 서로 다른 쓰레드에서 호출될 때 , 접근하는
전역 메모리를 다르게 설정하는 것이 가능합니다 .
전역 변수를 각 쓰레드별로 나누어서 해당 쓰레드에서 접근하도록 하는
방법과의 차이점은 다음과 같습니다 .
– 쓰레드를 위한 전역 변수를 직접 관리하면 각 쓰레드에 dependent 한 코드가 쓰레
드 루틴에 추가되어야 합니다 .
– Tls 를 사용하면 코드는 쓰레드와 상관없이 하나의 코드를 사용합니다 .
Boost 는 각 플랫폼에 대한 Tls 구현을 숨긴
''boost::thread_specifix_ptr<>'' 을 제공합니다 .
– 각 쓰레드에서 필요한 메모리인 경우 boost::thread_specifix_ptr<> 타입으로 변수
를 선언합니다 .
– 아래 코드에서 문제가 된 부분은 FindNearestSplinePoint() 함수가 단일 쓰레드에
서만 사용하다가 멀티 쓰레드에서 사용하게 되면서 이 함수가 내부에서 사용할 용도
로 선언된 static 변수의 read/wirte 동작 때문에 크래시가 발생한 경우 였습니다
.
– 그래서 이 함수가 사용하는 static 변수를 thread safe 하게 만들어 주어야 했습
니다 .
71
72. /// TLS(Thread Local Storage) 를 사용하여 각 thread 가 자신의 local memory 를 사용하도록 수정하
다 .
/// KpSplineUtil namespace 의 함수들은 thread safe 해야 한다 .
/// native type 이 아니므로 ''boost::thread_specific_ptr'' 를 이용한다 .
/// - jintaeks on 2013-03-20, 13:35 */
static boost::thread_specific_ptr<KpIntervalHeap<KInfo>> s_kIntervalHeap;
bool KpSplineUtil::FindNearestSplinePoint(
IN OUT KpSplinePosition& kInOutArgument_
, const KpSpline& kInSpline_
, const KpVector3& vInPoint_
, KpReal rInError_ )
{
unsigned uNumSegs = kInSpline_.GetNumSegments();
ASSERT( uNumSegs > 0 );
if( uNumSegs == 0 )
{
kInOutArgument_.Invalidate();
return false;
}//if
72
73. KpIntervalInfo<KInfo> kInfo;
KpVector3 vMin, vMax;
/// 각 thread 에서 처음으로 호출될 때 , 이 값은 NULL 이다 .
/// 그 때 thread 가 사용할 메모리를 할당한다 .
/// 프로그램 종료할 때 , 메모리 delete 를 시켜주어야 한다 .
/// - jintaeks on 2013-03-20, 13:37
if( s_kIntervalHeap.get() == NULL )
{
s_kIntervalHeap.reset( new KpIntervalHeap<KInfo>() );
}//if
s_kIntervalHeap->MakeEmpty();
if( kInOutArgument_.IsValid() )
{
ASSERT( kInOutArgument_.m_iIndex < ( int ) uNumSegs );
73
74. native 타입인 경우는 Visual Studio 2005 부터 지원
하는 확장 키워드 __declspec( thread ) 를 사용하
여 변수를 선언하면 됩니다 .
코드 상으로 각 쓰레드가 같은 변수를 접근하는 것
같지만 , 각 쓰레드별로 다른 메모리 위치를 접근하
게 됩니다 .
74
75. /// TLS(Thread Local Storage) 를 사용하여 각 thread 가 자신의 local memory 를 사용하도록 수정하
다 .
/// KpSplineUtil namespace 의 함수들은 thread safe 해야 한다 .
/// native type 인 경우는 Visual Studio 의 확장 기능인 __declspec( thread ) 를 명시하기만 하면
된다 .
/// - jintaeks on 2013-03-20, 13:35
__declspec( thread ) static KpReal s_rBITolerance2;
__declspec( thread ) static KpReal s_rBIWidth;
__declspec( thread ) static KpReal s_rBIHeight;
__declspec( thread ) static KpReal s_rBIntersectionT;
__declspec( thread ) static KpReal s_rBMinDist2;
static bool _FindBezierRectangleIntersection(
const KpBezierControl& kInBezier_
, KpReal rInTBegin_
, KpReal rInTEnd_ )
{
{
KpVector3 vMin, vMax;
_CalcBezierControlAABB( vMin, vMax, kInBezier_ );
if( vMin.X() > s_rBIWidth || vMax.X() < KpReal( 0.0 )
75
80. Atomic
연산의 중간 과정을 결과로 얻을 수 없다면 atomic
하다고 합니다 .
중간 과정을 얻는 것을 data race 라고 하며 , data
race 의 결과 torn read/torn write 가 발생합니다
.
non-atomic 이 발생하는 이유는 연산이 여러 개의
Cpu 명령으로 분리되기 때문입니다 ( 하나의 Cpu 명령
자체가 atomic 하지 않는 경우도 있습니다 ).
80
81. Atomic
simple type 에 대한 aligned 된 read/write 는
atomic 합니다 .
Win32 의 _InterlockedIncrement(), C++11 의
std::atomic<int>::fetch_add() 는 atomic RMW 의
예입니다 .
std::atomic<> 은 lock-free 를 보장하지 않습니다 .
std::atomic<>::is_lock_free() 로 검사해야 합니다
.
81
82. RMW 의 가장 흔한 예는 Compare-And-Swap(CAS) 입니
다 .
_InterlockedCompareExchange() 는 Win32 의 CAS 구
현 intrinsic 함수입니다 .
– intrinsic function 의 의미는 라이브러리가 제공하는 함수가 아니라
컴파일러가 제공하는 함수라는 의미입니다 .
82
83. Lock-free Programming
일반적으로 mutex 를 쓰지 않는 프로그래밍 기법이라
고 알려져 있습니다 .
mutex 의 사용 여부와 상관없이 하나의 쓰레드가 다
른 쓰레드를 영구히 block 시킬 수 없다면 lock-
free 하다고 합니다 .
83
84. 예 ) lock-free queue 의 push 구현
void LockFreeQueue::push(Node* newHead)
{
for (;;)
{
// Copy a shared variable (m_Head) to a local.
Node* oldHead = m_Head;
// Do some speculative work, not yet visible to other threads.
newHead->next = oldHead;
// Next, attempt to publish our changes to the shared variable.
// If the shared variable hasn't changed, the CAS succeeds and we return.
// Otherwise, repeat.
if (_InterlockedCompareExchange(&m_Head, newHead, oldHead) == oldHead)
return;
}
}
84
_InterlockedCompareExchange() 의 리턴값을 비교하는 짧은 순간에 ,
다른 쓰레드가 A 값을 B 로 바꾼 다음 다시 A 로 변경되었을 가능성이
있습니다 .
CAS 는 ABA 문제 (ABA problem) 가 발생하지 않도록 조심스럽게 코딩해야
합니다 .
86. Memory Ordering
atomic 한 일련의 연산이 보장된다고 , multi-core 에
서 동작하는 multi-threaded 프로그램의 data race
가 보장되지는 않습니다 .
왜냐하면 compiler 에 의해 , 실행 시간 Cpu 에 의해
연산의 순서가 바뀔 수 있기 때문입니다 .
프로세스는 여러가지 이유로 메모리 연산의 순서
order 를 바꿀 수 있습니다 .
86
87. volatile bool Ready = false;
int Value = 0;
// Thread A
while(!Ready) {}
printf("%d", Value);
// Thread B
Value = 1;
Ready = true;
87
예상되는 Value 의 결과는 1 입니다 .
하지만 , Ready = true; 가 Value = 1; 보다 먼저 실
행된다면 ?
88. std::atomic<> 에는 6 개의 memory ordering 옵션
이 있습니다 .
하지만 , 3 가지 memory ordering 모델중 한가지를
나타냅니다 .
– squentially-consistent ordering (memory_order_seq_cst)
– acquire-release ordering (memory_order_consume,
memory_order_acquire, memory_order_release, and
memory_order_acq_rel)
– relaxed ordering (memory_order_relaxed).
88
89. Memory Barrier
pending 된 메모리 연산을 완료하도록 하는 일련의
명령들을 말합니다 .
acquire, release, fence 의 세종류가 있습니다 .
89
90. Acquire semantics
operation 1
operation 2
<-operation 3-Acquire-> 3 is visible before 4-5
operation 4
operation 5
90
데이터에 접근하기 위해서 atomic 연산을 사용할 때
, 다른 process 가 변경될 값들의 연산을 실행하기
전에 lock 을 볼 수 있어야 합니다 .
이것을 acquire semantic 이라고 합니다 . 데이터를
접근하기 위한 권한을 얻을려고 acquire 하고 때문입
니다 .
91. Release semantics
operation 1
operation 2
<-operation 3-Release-> 1-2 are visible before 3
operation 4
operation 5
91
atomic 연산이 최근에 변경된 값들을 release 하려고
할 때 , 새로운 값은 release 전에 다른 process 에
게 보여야 합니다 .
이것을 release semantic 이라고 합니다 .
92. Fence semantics
operation 1
operation 2
<-operation 3-Fence-> 1-2 are visible before 3, 3
is visible before 4-5
operation 4
operation 5
92
fence 는 full memory barrier 라고도 합니다 .
93. class SpinLock
{
volatile tInt LockSem;
public:
FORCEINLINE SpinLock()
: LockSem(0)
{}
FORCEINLINE tBool Lock()
{
while(1)
{
// Atomically swap the lock variable with 1 if it's currently equal to 0
if(!InterlockedCompareExchange(&LockSem, 1, 0))
{
// We successfully acquired the lock
ImportBarrier();
return;
}
}
}
FORCEINLINE void Unlock()
{
ExportBarrier();
LockSem = 0;
}
};
93
94. volatile 로 지정된 변수에 write 하는 것은
release semantic 과 같습니다 .
volotile 로 지정된 변수에서 읽는 것은 acquire
semantic 과 같습니다 .
94
95. 예 ) 실제 문제 상황
HANDLE beginSema1;
HANDLE beginSema2;
HANDLE endSema;
int X, Y;
int r1, r2;
95
DWORD WINAPI thread1Func(LPVOID param)
{
MersenneTwister random(1);
for (;;)
{
WaitForSingleObject(beginSema1, INFINITE); // Wait for signal
while (random.integer() % 8 != 0) {} // Random delay
// ----- THE TRANSACTION! -----
X = 1;
#if USE_CPU_FENCE
MemoryBarrier(); // Prevent CPU reordering
#else
_ReadWriteBarrier(); // Prevent compiler reordering only
#endif
r1 = Y;
ReleaseSemaphore(endSema, 1, NULL); // Notify transaction
complete
}
return 0; // Never returns
};
96. DWORD WINAPI thread2Func(LPVOID param)
{
MersenneTwister random(2);
for (;;)
{
WaitForSingleObject(beginSema2, INFINITE); // Wait for signal
while (random.integer() % 8 != 0) {} // Random delay
// ----- THE TRANSACTION! -----
Y = 1;
#if USE_CPU_FENCE
MemoryBarrier(); // Prevent CPU reordering
#else
_ReadWriteBarrier(); // Prevent compiler reordering only
#endif
r2 = X;
ReleaseSemaphore(endSema, 1, NULL); // Notify transaction complete
}
return 0; // Never returns
};
96
97. #if USE_SINGLE_HW_THREAD
// Force thread affinities to the same cpu core.
SetThreadAffinityMask(thread1, 1);
SetThreadAffinityMask(thread2, 1);
#endif
// Repeat the experiment ad infinitum
int detected = 0;
for (int iterations = 1; ; iterations++)
{
// Reset X and Y
X = 0;
Y = 0;
// Signal both threads
ReleaseSemaphore(beginSema1, 1, NULL);
ReleaseSemaphore(beginSema2, 1, NULL);
// Wait for both threads
WaitForSingleObject(endSema, INFINITE);
WaitForSingleObject(endSema, INFINITE);
// Check if there was a simultaneous reorder
if (r1 == 0 && r2 == 0)
{
detected++;
printf("%d reorders detected after %d iterationsn", detected, iterations);
}
}
97
98. 어떻게 해결하나요 ?
std::atomic<int> X(0), Y(0);
int r1, r2;
void thread1()
{
X.store(1);
r1 = Y.load();
}
void thread2()
{
Y.store(1);
r2 = X.load();
}
98
std::atomic<> 은 atomic 연산과 memory barrier 를
지원하는 C++11 의 표준 라이브러리입니다 .
.store() 와 .load() 는 디폴트로 fence 를 설치합니
다 .
100. Relaxed ordering
std::atomic<int> x;
std::atomic<int> y;
// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B
// Thread 2:
r2 = x.load(memory_order_relaxed); // C
y.store(42, memory_order_relaxed); // D
100
is allowed to produce r1 == r2 == 42 because, although A
is sequenced-before B and C is sequenced before D, nothing
prevents D from appearing before A in the modification order of
y, and B from appearing before C in the modification order of x.
101. 예 ) counter
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> cnt = {0};
void f()
{
for (int n = 0; n < 1000; ++n) {
cnt.fetch_add(1, std::memory_order_relaxed);
}
}
int main()
{
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f);
}
for (auto& t : v) {
t.join();
}
std::cout << "Final counter value is " << cnt << 'n';
}
101
102. Release-Acquire ordering
If an atomic store in thread A is
tagged std::memory_order_release and an atomic
load in thread B from the same variable is
tagged std::memory_order_acquire, all memory writes
(non-atomic and relaxed atomic) that happened-
before the atomic store from the point of view of
thread A, become visible side-effects in thread B, that
is, once the atomic load is completed, thread B is
guaranteed to see everything thread A wrote to
memory.
기다리는 B 의 atomic load 이후의 명령들이 , A 의
atomic store 전에 처리한 모든 값을 볼 수 있습니다
.102
103. std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
103
104. Sequentially-consistent ordering
Atomic operations
tagged std::memory_order_seq_cst not only order
memory the same way as release/acquire ordering
(everything that happened-before a store in one
thread becomes a visible side effect in the
thread that did a load), but also establish a
single total modification order of all atomic operations
that are so tagged.
core 가 1 개인 경우 명령들이 정렬되어 실행되는 경
우와 같습니다 .
– fence(full memory barrier) 를 생성합니다 .
104
110. 멀티쓰레드 프로그래밍 :
04. Parallel Pattern Library
2014 년 11 월 9 일
jintaeks@gmail.com
111. 목차 index
3 회
– atomic
– lock-free
– memory ordering
– std::atomic<>
4 회 : PPL, Parallel Pattern Library
– Task Parallelism (Concurrency Runtime)
– Parallel Algorithms
– Parallel Containers and Objects
– Cancellation in the PPL
– Debugging a Parallel Program
5 회 : C++ AMP
6 회 : 각 팀의 Thread 사용 현황
– 각 팀에서 사용중인 쓰레드
– PPL 적용 개선 가능한 것들
111
113. Concurrency Runtime
자체의 thread pool 을 유지합니다 .
Concurrency runtime 은 work-stealing 알고리즘으
로 각 쓰레드의 load 를 조정합니다 .
Concurrency runtime 은 리소스 접근의 동기화를 위
해 서로 협동하는 blocking primitive 를 제공합니
다 .
– Parallel Pattern Library
– Asynchronous Agene Library
– Task Scheduler
– Resource Manager
113
114. PPL, Parallel Pattern Library
Ppl 은 일반적인 목적의 parallel container 와
algorithm 을 제공합니다 .
Ppl 은 parallel algorithm 을 통해 data
parallelism 을 제공합니다 .
Ppl 은 task 를 통해 task parallelism 을 제공합니
다 .
114
115. Asynchronous Agent Library
Actor-based programming 을 제공합니다 .
Message passing interface 를 제공합니다 .
아래 링크를 참조하세요 .
– http://msdn.microsoft.com/en-us/library/dd492627.aspx
115
116. Task Scheduler
Task Scheduler 는 실행시간에 task 를 스케쥴링하고
조정 coordinate 합니다 .
Processing 리소스를 최대한으로 사용하기 위해
work-stealing 알고리즘을 사용합니다 .
116
117. Resource Manager
컴퓨팅 리소스 , 즉 프로세서 processor 와 메모리를
관리합니다 .
가장 최적이 되도록 리소스를 할당합니다 .
Task Scheduler 와 상호작용하면서 리소스에 대한 추
상 계층을 제공합니다 .
117
118. 람다 lambda
In mathematical logic and computer science, lambda is used to
introduce anonymous functions expressed with the concepts of lambda
calculus.
118
119. 람다 : syntax
a. lambda-introducer
(capture clause)
b. lambda declarator
(parameter list)
c. mutable (mutable
specification)
d. exception-
specification
(exception
specification)
e. trailing-return-type
(return type)
f. compound-statement
119
120. 람다 : example
int main()
{
using namespace std;
// Assign the lambda expression that adds two numbers to an auto variable.
auto f1 = [](int x, int y) { return x + y; };
cout << f1(2, 3) << endl;
// Assign the same lambda expression to a function object.
function<int(int, int)> f2 = [](int x, int y) { return x + y; };
cout << f2(3, 4) << endl;
}
120
121. PPL 의 Task Parallelism
parallel pattern library
121
122. Ppl 예 ) 피보나치 수열 계산
// Calls the provided work function and returns the number of milliseconds
// that it takes to call that function.
template <class Function>
__int64 time_call( Function&& f )
{
__int64 begin = GetTickCount();
f();
return GetTickCount() - begin;
}
// Computes the nth Fibonacci number.
int fibonacci( int n )
{
if( n < 2 )
return n;
return fibonacci( n - 1 ) + fibonacci( n - 2 );
}
122
123. 직렬처리 serial processing
__int64 elapsed;
// An array of Fibonacci numbers to compute.
std::array<int, 4> a = { 24, 26, 41, 42 };
// The results of the serial computation.
std::vector<std::tuple<int, int>> results1;
// Use the for_each algorithm to compute the results serially.
elapsed = time_call( [&]
{
std::for_each (std::begin(a), std::end(a), [&]( int n ) {
results1.push_back( std::make_tuple( n, fibonacci( n ) ) );
});
});
std::wcout << L"serial time: " << elapsed << L" ms" << std::endl;
123
124. 병렬처리 parallel processing
// The results of the parallel computation.
concurrency::concurrent_vector<std::tuple<int, int>> results2;
// Use the parallel_for_each algorithm to perform the same task.
elapsed = time_call( [&]
{
concurrency::parallel_for_each( std::begin(a), std::end(a), [&]( int n ) {
results2.push_back( std::make_tuple( n, fibonacci( n ) ) );
});
// Because parallel_for_each acts concurrently, the results do not
// have a pre-determined order. Sort the concurrent_vector object
// so that the results match the serial version.
std::sort( std::begin( results2 ), std::end( results2 ) );
});
std::wcout << L"parallel time: " << elapsed << L" ms" << std::endl << std::endl;
124
/** Output
serial time: 9250
ms
parallel time:
5726 ms
fib(24): 46368
fib(26): 121393
fib(41): 165580141
fib(42): 267914296
*/
125. concurrency::task<>
#include <ppltasks.h>
#include <iostream>
//using namespace concurrency;
//using namespace std;
int wmain()
{
// Create a task.
concurrency::task<int> t( []()
{
return 42;
});
// In this example, you don't necessarily need to call wait() because
// the call to get() also waits for the result.
t.wait();
// Print the result.
std::wcout << t.get() << std::endl;
}
/* Output:
42
*/
125
concurrency::task<> 를 사용해 태
스크를 정의합니다 . task 의 템플
릿 인자는 태스크의 리턴타입 입
니다 .
wait() 는 태스크가 실행된 경우 ,
태스크의 종료를 기다립니다 .
get() 은 태스크의 종료시 리턴값을
얻습니다 .
126. concurrency::task::create_task(), then()
concurrency::task<std::wstring> write_to_string()
{
// Create a shared pointer to a string that is assigned to and read by multiple tasks.
// By using a shared pointer, the string outlives the tasks, which can run in the
background after
// this function exits.
auto s = std::make_shared<std::wstring>(L"Value 1");
return concurrency::create_task([s]
{
// Print the current value.
std::wcout << L"Current value: " << *s << std::endl;
// Assign to a new value.
*s = L"Value 2";
}).then([s]
{
// Print the current value.
std::wcout << L"Current value: " << *s << std::endl;
// Assign to a new value and return the string.
*s = L"Value 3";
return *s;
126
태스크를 생성하기 위해
create_task() 를 사용합니다 .
연속된 태스크는 task<> 의
then() 을 사용합니다 .
127. lambda 는 thread-safe 해야 합니다 .
// lambda-task-lifetime.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <string>
…
int wmain()
{
// Create a chain of tasks that work with a string.
auto t = write_to_string();
// Wait for the tasks to finish and print the result.
std::wcout << L"Final value: " << t.get() << std::endl;
}
/* Output:
Current value: Value 1
Current value: Value 2
Final value: Value 3
*/127
태스크의 동작은 thread-safe 해야
합니다 . 그러므로 람다함수는
thread-safe 한 동작이 보장되도
록 적절하게 변수를 capture 해
야 합니다 .
예에서 string 은
write_to_string() 이 리턴된 이
후에도 유효해야 하므로
std::shared_ptr<> 로 관리하고
있습니다 .
130. task continuation
int wmain()
{
auto t = concurrency::create_task([]() -> int
{
return 0;
});
// Create a lambda that increments its input value.
auto increment = [](int n) { return n + 1; };
// Run a chain of continuations and print the result.
int result = t.then(increment).then(increment).then(increment).get();
std::wcout << result << std::endl;
}
/* Output:
3
*/
130
131. task in task
int wmain()
{
auto t = concurrency::create_task([]()
{
std::wcout << L"Task A" << std::endl;
// Create an inner task that runs before any continuation
// of the outer task.
return concurrency::create_task([]()
{
std::wcout << L"Task B" << std::endl;
});
});
// Run and wait for a continuation of the outer task.
t.then([]()
{
std::wcout << L"Task C" << std::endl;
}).wait();
}
131
/* Output:
Task A
Task B
Task C
*/
132. when_all()
int wmain()
{
// Start multiple tasks.
std::array<concurrency::task<void>, 3> tasks =
{
concurrency::create_task([] { std::wcout << L"Hello from taskA." << std::endl; }),
concurrency::create_task([] { std::wcout << L"Hello from taskB." << std::endl; }),
concurrency::create_task([] { std::wcout << L"Hello from taskC." << std::endl; })
};
auto joinTask = concurrency::when_all( std::begin(tasks), std::end(tasks) );
// Print a message from the joining thread.
std::wcout << L"Hello from the joining thread." << std::endl;
// Wait for the tasks to finish.
joinTask.wait();
}
132
/* Sample output:
Hello from the joining
thread.
Hello from taskA.
Hello from taskC.
Hello from taskB.
*/
when_all 은 task<std::vector<T>>
를 리턴합니다 .
133. when_all() : get returns
int wmain()
{
// Start multiple tasks.
std::array<concurrency::task<int>, 3> tasks =
{
concurrency::create_task([]() -> int { return 88; }),
concurrency::create_task([]() -> int { return 42; }),
concurrency::create_task([]() -> int { return 99; })
};
auto joinTask = concurrency::when_all( std::begin(tasks), std::end(tasks) ).then([]( std::vector<int>
results )
{
std::wcout << L"The sum is "
<< std::accumulate( std::begin(results), std::end(results), 0 )
<< L'.' << std::endl;
});
// Print a message from the joining thread.
std::wcout << L"Hello from the joining thread." << std::endl;
// Wait for the tasks to finish.
joinTask.wait();
}133
/* Output:
Hello from the joining
thread.
The sum is 229.
*/
134. when_any()
int wmain()
{
// Start multiple tasks.
std::array<concurrency::task<int>, 3> tasks = {
concurrency::create_task([]() -> int { return 88; }),
concurrency::create_task([]() -> int { return 42; }),
concurrency::create_task([]() -> int { return 99; })
};
// Select the first to finish.
concurrency::when_any( std::begin(tasks), std::end(tasks)).then([]( std::pair<int, size_t>
result)
{
std::wcout << "First task to finish returns "
<< result.first
<< L" and has index "
<< result.second
<< L'.' << std::endl;
}).wait();
}
134
/* Sample output:
First task to finish returns 42 and has
index 1.
*/
when_any() 는 최초로 완료된 태스크
의 std::pair< 리턴값 ,index> 를
리턴합니다 .
135. task group
태스크의 집합을 관리합니다 .
– 태스크 그룹은 태스크를 work-stealing 큐에 push 합니다 .
– 그룹의 각 태스크는 concurrency::task_handl 로 접근합니다 .
structured task group
– 그룹의 연산은 같은 쓰레드에서 일어나야 합니다 .
• cancel() 과 is_cancelling() 만 예외입니다 .
– wait() 호출 이후에 태스크를 추가하면 안 됩니다 .
– 쓰레드 간의 동기화를 하지 않으므로 task_group 보다 오버헤드가
적습니다 .
unstructured task group
concurrency::parallel_invoke() 는
structured_task_group 을 사용합니다 .
태스크 그룹은 cancellation 을 지원합니다 .
135
136. structured_task_group
int wmain()
{
// Use the make_task function to define several tasks.
auto task1 = concurrency::make_task([] { /*TODO: Define the task body.*/ });
auto task2 = concurrency::make_task([] { /*TODO: Define the task body.*/ });
auto task3 = concurrency::make_task([] { /*TODO: Define the task body.*/ });
// Create a structured task group and run the tasks concurrently.
concurrency::structured_task_group tasks;
tasks.run( task1 );
tasks.run( task2 );
tasks.run_and_wait( task3 );
}
136
make_task 는 task 를 정의만 하고
실행하지 않습니다 .
structured task group 의
run_and_wait() 는 group 의 모
든 task 가 종료하기를 기다립니
다 .
137. parallel_invoke
template <typename T>
T twice( const T& t )
{
return t + t;
}
int wmain()
{
// Define several values.
int n = 54;
double d = 5.6;
std::wstring s = L"Hello";
// Call the twice function on each value concurrently.
// parallel_invoke uses structured_task_group internally. jintaeks on 20141107
concurrency::parallel_invoke(
[&n] { n = twice(n); },
[&d] { d = twice(d); },
[&s] { s = twice(s); }
);
// Print the values to the console.
std::wcout << n << L' ' << d << L' ' << s << std::endl;
}137
/** Output
108 11.2 HelloHello
*/
parallel_invoke() 는 내부적으로
structured_task_group 을 이용
합니다 .
138. unstructured task_group
int wmain()
{
// A task_group object that can be used from multiple threads.
concurrency::task_group tasks;
// Concurrently add several tasks to the task_group object.
concurrency::parallel_invoke(
[&] {
// Add a few tasks to the task_group object.
tasks.run([] { print_message(L"Hello"); });
tasks.run([] { print_message(42); });
},
[&] {
// Add one additional task to the task_group object.
tasks.run([] { print_message(3.14); });
}
);
// Wait for all tasks to finish.
tasks.wait();
}
138
/** Output:
Message from task: Hello
Message from task: 3.14
Message from task: 42
*/
non-structured task group 는 서로
다른 thread 에서 group 를 접근
해서 task 를 관리할 수 있습니다
.
139. Cancellation
concurrency::cancellation_token_source cts;
auto token = cts.get_token();
std::wcout << L"Creating task..." << std::endl;
// Create a task that performs work until it is canceled.
auto t = concurrency::create_task( []
{
bool moreToDo = true;
while( moreToDo )
{
// Check for cancellation.
if( concurrency::is_task_cancellation_requested() )
{
// TODO: Perform any necessary cleanup here...
// Cancel the current task.
concurrency::cancel_current_task();
}
else
{
// Perform work.
moreToDo = do_work();
}
}
}, token );
139
140. // Wait for one second and then cancel the task.
concurrency::wait( 1000 );
std::wcout << L"Canceling task..." << std::endl;
cts.cancel();
// Wait for the task to cancel.
std::wcout << L"Waiting for task to complete..." << std::endl;
t.wait();
std::wcout << L"Done." << std::endl;
140
/* Sample output:
Creating task...
Performing work...
Performing work...
Performing work...
Performing work...
Canceling task...
Waiting for task to complete...
Done.
*/
141. Cancellation callback
concurrency::cancellation_token_source cts;
auto token = cts.get_token();
// An event that is set in the cancellation callback.
concurrency::event e;
concurrency::cancellation_token_registration cookie;
cookie = token.register_callback( [&e, token, &cookie]()
{
std::wcout << L"In cancellation callback..." << std::endl;
e.set();
// Although not required, demonstrate how to unregister
// the callback.
token.deregister_callback(cookie);
} );
141
142. std::wcout << L"Creating task..." << std::endl;
// Create a task that waits to be canceled.
auto t = concurrency::create_task([&e]
{
e.wait();
}, token );
// Cancel the task.
std::wcout << L"Canceling task..." << std::endl;
cts.cancel();
// Wait for the task to cancel.
t.wait();
std::wcout << L"Done." << std::endl;
142
/* Sample output:
Creating task...
Canceling task...
In cancellation callback...
Done.
*/
143. Task tree
// Create a task group that serves as the root of the tree.
concurrency::structured_task_group tg1;
// Create a task that contains a nested task group.
auto t1 = concurrency::make_task([&] {
concurrency::structured_task_group tg2;
std::wcout << L"t1 task" << std::endl;
// Create a child task.
auto t4 = concurrency::make_task([&] {
std::wcout << L"t4 task" << std::endl;
});
// Create a child task.
auto t5 = concurrency::make_task([&] {
std::wcout << L"t5 task" << std::endl;
});
// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();
});143
144. // Create a child task.
auto t2 = concurrency::make_task([&] {
std::wcout << L"t2 task" << std::endl;
});
// Create a child task.
auto t3 = concurrency::make_task([&] {
std::wcout << L"t3 task" << std::endl;
});
// Run the child tasks and wait for them to finish.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);
tg1.wait();
144
150. 예 ) create_task_tree()
// Create a task group that serves as the root of the tree.
concurrency::structured_task_group tg1;
// Create a task that contains a nested task group.
auto t1 = concurrency::make_task([&] {
concurrency::structured_task_group tg2;
std::wcout << L"t1 task" << std::endl;
// Create a child task.
auto t4 = concurrency::make_task([&] {
std::wcout << L"t4 task" << std::endl;
});
// Create a child task.
auto t5 = concurrency::make_task([&] {
std::wcout << L"t5 task" << std::endl;
});
// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();
});150
151. // Create a child task.
auto t2 = concurrency::make_task([&] {
std::wcout << L"t2 task" << std::endl;
});
// Create a child task.
auto t3 = concurrency::make_task([&] {
std::wcout << L"t3 task" << std::endl;
});
// Run the child tasks and wait for them to finish.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);
tg1.wait();
151