Skip to content

Instantly share code, notes, and snippets.

@ssfang
Last active October 10, 2016 12:02
Show Gist options
  • Save ssfang/03b8f22beba0c4ef811418086a774fd0 to your computer and use it in GitHub Desktop.
Save ssfang/03b8f22beba0c4ef811418086a774fd0 to your computer and use it in GitHub Desktop.
C++11基础心得与多线程编程外加主流服务端语言Java的互融

例子

#include <stdio.h>
#include <stdlib.h>

#include <iostream> // std::cout
#include <thread>   // std::thread
#include <string>
#include <list>
#include <list>
#include <vector>
#include <chrono>
#include <memory>
#include <mutex>

std::mutex g_display_mutex;

struct Foo {
public:
	int n;
	std::string s;
	Foo() {
		std::cout << "Foo" << std::endl;
	}
	Foo(int i) :n(i) {
		std::cout << "Foo(int i)" << i << std::endl;
		s = "s=" + std::to_string(i);
	}
};

void thread_task(Foo& f, std::string name) {
	auto threadid = std::this_thread::get_id();

	g_display_mutex.lock();
	std::cout << "[thread#" << threadid << name << "]Entry." << std::endl;
	g_display_mutex.unlock();

	std::this_thread::sleep_for(std::chrono::seconds(2));

	{
		// http://en.cppreference.com/w/cpp/thread/lock_guard
		std::lock_guard<std::mutex> lock(g_display_mutex); // RAII-style
		std::cout << "[thread#" << threadid << name << "]: " << f.s << std::endl;
		std::cout << "[thread#" << threadid << name << "]Leave." << std::endl;
	}
}

void thread_task_with_shared_parameter(std::shared_ptr<Foo> f, std::string name) {
	thread_task(*f, name);
}

int main(int argc, const char *argv[])
{
	unsigned int n = std::thread::hardware_concurrency();
	std::cout << n << " concurrent threads are supported.\n";

	std::thread not_a_thread;
	{
		Foo stack_foo(22);
		//http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared
		auto shared_foo = std::make_shared<Foo>(66);

		std::thread t2(thread_task_with_shared_parameter, shared_foo, "shared parameter");

		{
			std::lock_guard<std::mutex> lock(g_display_mutex); // RAII-style
			std::cout << "underlying thread handle: " << t2.native_handle() << ", joinable: " << t2.joinable() << std::endl;
		}

		std::thread t3(thread_task, std::ref(stack_foo), "stack parameter");

		// http://en.cppreference.com/w/cpp/thread/thread/join
		// Blocks the current thread until the thread identified by *this finishes its execution.
		//t2.join();
		//t3.join(); //stack_foo will be valid in thread_task

		// http://en.cppreference.com/w/cpp/thread/thread/detach
		// After calling detach *this no longer owns any thread.
		// So, it
		t2.detach();
		t3.detach(); // stack_foo will be invalid in thread_task

		{
			std::lock_guard<std::mutex> lock(g_display_mutex); // RAII-style
			std::cout << "std::thread created with shared parameter is" << (t2.joinable()? "" : " not ") << "joinable" << std::endl;
			std::cout << "std::thread created with stack parameter is" << (t3.joinable() ? "" : " not ") << "joinable" << std::endl;
		}

		/*
			// VS 2015
			~thread() _NOEXCEPT
			{	// clean up
				if (joinable())
					_XSTD terminate();
			}
		*/
	}
	g_display_mutex.lock();
	std::cout << "All threads should ~thread()." << std::endl;
	g_display_mutex.unlock();

	std::vector<std::string> myvector = { "10", "20","30" };

	myvector.emplace_back("100");
	myvector.emplace_back("200");

	std::cout << "myvector contains:";
	for (auto& x : myvector)
		std::cout << ' ' << x.c_str();
	std::cout << '\n';

	getchar();
	return EXIT_SUCCESS;
}

上面

t2.detach(); t3.detach(); outputted [thread#9912stack parameter]:

2 concurrent threads are supported.
Foo(int i)22
Foo(int i)66
underlying thread handle: 00000048, joinable: 1
[thread#9492shared parameter]Entry.
std::thread created with shared parameter is not joinable
std::thread created with stack parameter is not joinable
All threads should ~thread().
myvector contains: 10 20 30 100 200
[thread#9912stack parameter]Entry.
[thread#9492shared parameter]: s=66
[thread#9492shared parameter]Leave.
[thread#9912stack parameter]:
[thread#9912stack parameter]Leave.

t2.join(); t3.join(); outputted [thread#6676stack parameter]: s=22

2 concurrent threads are supported.
Foo(int i)22
Foo(int i)66
underlying thread handle: 00000048, joinable: 1
[thread#6004shared parameter]Entry.
[thread#6676stack parameter]Entry.
[thread#6004shared parameter]: s=66
[thread#6004shared parameter]Leave.
[thread#6676stack parameter]: s=22
[thread#6676stack parameter]Leave.
std::thread created with shared parameter is not joinable
std::thread created with stack parameter is not joinable
All threads should ~thread().
myvector contains: 10 20 30 100 200

问题:应避免访问脱离作用域的变量 原因:脱离作用域这里指主要只传递生命周期短的变量(如栈上变量)的引用或地址给周期长的线程使用。

这里也有一篇文章C++11 Multithreading – Part 3: Carefully Pass Arguments to Threads

二叉堆/堆

堆一般都用数组来表示堆(cppreference上用RandomIt和cplusplus上用RandomAccessIterator也做了暗示)。

堆与堆排序
二叉堆二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆/小顶堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆/大顶堆。

max heap example

storage structure: array, 5 4 3 1 2

logic structure: 
     5
   /   \
  4     3
 / \    /
3   1  2

可见,一个数组构成一个有序最大(或最小)堆,那么数组中最大(或最小)的元素(在首位置)就是有序堆的根节点。

C++算法库之堆操作

头文件#include <algorithm>,提供了五个堆操作的方法heap methods,默认版本使用operator<比较元素。

“首、第一,尾、末、最后”,这些词汇后面直接跟着“位置”组合使用时表示包含靠前的(首、第一),而不包含靠后的(尾、末、最后),对应单词first, last,位置范围为[last, last);单独使用或后面直接跟着“元素”等表示范围内元素,如靠后词汇(尾、末、最后)对应单词back

  • std::is_heap 判断指定范围的元素是否按照最大堆排列,即堆化。
  • std::make_heap 对指定范围进行最大堆排列
  • std::pop_heap 最大值(在最前位置上的值)移动到最后元素位置(即,首尾元素交换),而最后元素位置之前的元素按最大堆重新排列。事后最大元素在最后,最大堆范围减1。
#include <iostream>     // std::cout
#include <algorithm>    // std::make_heap, std::pop_heap, std::push_heap, std::sort_heap
#include <vector>       // std::vector
int main () {
 std::vector<int> ints{2, 1, 3, 4};
 std::make_heap(ints.begin(), ints.end()); //after rearranged, 4 2 3 1
 std::pop_heap(ints.begin(), ints.end()); // after pop_heap, 3 2 1 4
 // 3 2 1 is max heap. 4 is the highest value in the position first before pop_heap.
 std::cout << ints.back() << '\n'; // Returns reference to the last element in the container. Output: 4
 ints.pop_back(); // Removes the last element of the container.
 return 0;
}
  • std::push_heap 把最后元素插入到前面最大堆的相应位置使给定范围按最大堆排列。事前新增元素要先位于最后,事后最大堆范围加1。
#include <iostream>     // std::cout
#include <algorithm>    // std::make_heap, std::pop_heap, std::push_heap, std::sort_heap
#include <vector>       // std::vector
int main () {
 std::vector<int> ints{2, 1, 3, 4};
 std::make_heap(ints.begin(), ints.end()); //after rearranged, 4 2 3 1
 ints.push_back(5); // after push_back, 4 2 3 1 5
 std::push_heap(ints.begin(), ints.end()); // after push_heap, 5 4 3 1 2
 return 0;
}
  • std::sort_heap 升序排列一个最大堆,此排序已假定给定范围是最大堆
#include <iostream>     // std::cout
#include <algorithm>    // std::make_heap, std::pop_heap, std::push_heap, std::sort_heap
#include <vector>       // std::vector
int main () {
 std::vector<int> ints{2, 1, 3, 4};
 // std::make_heap(ints.begin(), ints.end()); //after rearranged, 4, 2, 3, 1
 std::sort_heap(ints.begin(), ints.end());
 for (auto i : ints) std::cout << i << ' ';
 std::cout << '\n';
 return 0;
}
// Output: 1 3 4 2

其中,std::pop_heapstd::push_heap都是使用最后元素进行操作。

#include <stdio.h>
#include <stdlib.h>
#include <iostream> // std::cout
#include <exception>
using namespace std;
struct MyException : public exception
{
MyException() {
std::cout << "MyException()" << std::endl;
}
MyException(MyException const& _Other)
{
exception::exception(_Other);
std::cout << "(MyException const& _Other)" << std::endl;
}
const char * what() const throw () override
{
return "MyException";
}
};
int main(int argc, const char *argv[])
{
try
{
//// 错误 C2148 数组的总大小不得超过 0x7fffffff 字节
//int* large = new int[0x7fffffff/sizeof(int)]; //std::bad_alloc
//// crash
//int* null = 0;
//*null = 22;
throw MyException();
}
catch (MyException& e) // First come first choice
{
std::cout << "MyException caught, " << e.what() << std::endl;
}
catch (std::exception& e)
{
//Other errors: e.g. std::bad_alloc(bad allocation)
std::cout << "std::exception caught, " << e.what() << std::endl;
}
catch (...)
{
cout << "default exception" << std::endl;
}
getchar();
return EXIT_SUCCESS;
}

C++模式 在时序中执行 C++ Patterns: Executing Around Sequences

Bjarne Stroustrup's homepage 本贾尼·斯特劳斯特卢普的个人官方主页 C++语言之父 More on A&& C++11FAQ 右值C++11 标准新特性: 右值引用与转移语义

 void process_value(int& i) { 
  std::cout << "LValue processed: " << i << std::endl; 
 } 

 void process_value(int&& i) { 
  std::cout << "RValue processed: " << i << std::endl; 
 } 

 void forward_value(int&& i) { 
  process_value(i); 
 } 

 int main() { 
  int a = 0; 
  process_value(a); 
  process_value(1); 
  forward_value(2); 
 }
 
 /*
 Output:
 LValue processed: 0 
 RValue processed: 1 
 LValue processed: 2
 */

模型

  • 非阻塞IO+IO多路复用 non-blocking IO + IO multiplexing
  • 线程池+队列 thread pool + queue (Producer-consumer BlockingQueue, ThreadPoolExecutor TaskQueue)
  • 每个IO线程有一个事件循环 one loop per thread

IO密集型和CPU密集型。在one loop per thread模型的event loop事件循环中使用non-blocking IO + IO multiplexing加上thread pool计算线程池可以较好的处理并发情况。

多线程下就要考虑,共享资源在线程间的同步。

锁:

  • 互斥锁(互斥器,英文:Mutual exclusion,互相排斥的,缩写Mutex)
  • 自旋锁
  • 也许还有自旋-互斥锁

互斥锁一般用于临界区持锁时间比较长的操作

Java中synchronized关键字(可重入/递归的,不用手动释放,临界区和语句块或方法的作用域一样,释放也是在脱离作用域后)和Lockjava.util.concurrent.locks.Lock接口实现类(是否可重入/可递归看具体子类,ReentrantLock顾名思义,必须手动释放)
对应C++11中的RAII风格std::mutex+std::lock_guard<std::mutex>(举这一个典型,自动释放,临界区和lock_guard一致)和std::mutex(是否可重入看平台吧,windows上Mutex可以的而且可跨进程,是个内核对象)。

Base Win Critical Section Win32 Mutex Linux Solaris JAVA
创建 CRITICAL_SECTION cs; InitializeCriticalSection(&cs); HANDLE hMutex; CreateMutex pthread_mutex_init mutex_init new XLock();
加锁 EnterCriticalSection(&cs); TryEnterCriticalSection(&cs); WaitForSingleObject(hMutex, INFINITE); WaitForSingleObject (hMutex, 0); WaitForSingleObject (hMutex, dwMilliseconds); pthread_mutex_lock mutex_lock lock() tryLock() tryLock(long time, TimeUnit unit) lockInterruptibly()
解锁 LeaveCriticalSection(&cs); ReleaseMutex(hMutex); pthread_mutex_unlock mutex_unlock unlock()
销毁 DeleteCriticalSection(&cs); CloseHandle(hMutex); pthread_mutex_destroy mutex_destroy N.A.(GC)
备注 性能较好;不可跨进程。 可重入(已获锁可重复调用加锁,加锁与解锁须对称) 内核对象,性能较差;可跨进程。可重入(加锁与解锁须对称) 可重入

Critical Section本身不是内核对象,相关函数(EnterCriticalSection,LeaveCriticalSection)的调用一般都在用户模式内执行,在x86处理器上一般只需要发费9个左右的 CPU指令周期。只有当想要获得的锁正好被别的线程拥有时才会退化成和Mutex一样,即转换到内核模式,发费600个左右的 CPU指令周期。

Mutex 是内核对象,相关函数的执行 (WaitForSingleObject,ReleaseMutex)需要用户模式(User Mode)到内核模式(Kernel Mode)的转换,在x86处理器上这种转化一般要发费600个左右的 CPU指令周期。

条件变量(condition variable)

虚拟机或语言实现级

Java中实现: Object的监视器方法 (wait, notifynotifyAll)和Condition(+Condition Lock.newCondition())。

系统平台API级

linux平台上

#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);  
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

正如陈硕所著说的:

Pthreads condition variable 允许在 wait() 的时候指定 mutex,但是我想不出什么理由一个 condition variable 会和不同的 mutex 配合使用。Java的 intrinsic condition 和 Conditon class 都不支持这么做,因此我觉得可以放弃这一灵活性,老老实实一对一好了。相反 boost::thread 的 condition_varianle 是在 wait 的时候指定 mutex,请参观其同步原语的庞杂设计:

Concept 有四种 Lockable, TimedLockable, SharedLockable, UpgradeLockable.
Lock 有五六种: lock_guard, unique_lock, shared_lock, upgrade_lock, upgrade_to_unique_lock, scoped_try_lock.
Mutex 有七种:mutex, try_mutex, timed_mutex, recursive_mutex, recursive_try_mutex, recursive_timed_mutex, shared_mutex.

恕我愚钝,见到 boost::thread 这样如 Rube Goldberg Machine 一样“灵活”的库我只得三揖绕道而行。这些 class 名字也很无厘头,为什么不老老实实用 reader_writer_lock 这样的通俗名字呢?非得增加精神负担,自己发明新名字。我不愿为这样的灵活性付出代价,宁愿自己做几个简简单单的一看就明白的 classes 来用,这种简单的几行代码的轮子造造也无妨。提供灵活性固然是本事,然而在不需要灵活性的地方把代码写死,更需要大智慧。

下面这个 Condition 简单地封装了 pthread cond var,用起来也容易,见本节前面的例子。这里我用 notify/notifyAll 作为函数名,因为 signal 有别的含义,C++ 里的 signal/slot,C 里的 signal handler 等等。就别 overload 这个术语了。

class Condition : boost::noncopyable
{
 public:
  Condition(MutexLock& mutex) : mutex_(mutex)
  { pthread_cond_init(&pcond_, NULL); }
 
  ~Condition()
  { pthread_cond_destroy(&pcond_); }
 
  void wait()
  { pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()); }
 
  void notify()
  { pthread_cond_signal(&pcond_); }
 
  void notifyAll()
  { pthread_cond_broadcast(&pcond_); }
 
 private:
  MutexLock& mutex_;
  pthread_cond_t pcond_;
};

如果一个 class 要包含 MutexLock 和 Condition,请注意它们的声明顺序和初始化顺序,mutex_ 应先于 condition_ 构造,并作为后者的构造参数:

class CountDownLatch
{
 public:
  CountDownLatch(int count)
   : count_(count),
     mutex_(),
     condition_(mutex_)
  { }
 
 private:
  int count_;
  MutexLock mutex_;  // 顺序很重要
  Condition condition_;
};

请允许我再次强调,虽然本节花了大量篇幅介绍如何正确使用 mutex 和 condition variable,但并不代表我鼓励到处使用它们。这两者都是非常底层的同步原语,主要用来实现更高级的并发编程工具,一个多线程程序里如果大量使用 mutex 和 condition variable 来同步,基本跟用铅笔刀锯大树(孟岩语)没啥区别。

在程序里使用 pthreads 库有一个额外的好处:分析工具认得它们,懂得其语意。线程分析工具如 Intel Thread Checker 和 Valgrind-Helgrind 等能识别 pthreads 调用,并依据 happens-before 关系 [Lamport 1978] 分析程序有无 data race。

Windows上

条件变量(Condition variables 需要Windows Vista及以上,Kernel32.dll,它可以与“关键代码段(critical section)”或“读写锁(SRWLock)”相互配合使用,来实现线程的同步,特别是实现类似“生产者-消费者”问题的时候,详见Using Condition Variables

CRITICAL_SECTION cs;
CONDITION_VARIABLE cv;

VOID WINAPI InitializeConditionVariable(
  _Out_ PCONDITION_VARIABLE ConditionVariable
);

BOOL SleepConditionVariableCS(
   PCONDITION_VARIABLE pConditionVariable,
   PCRITICAL_SECTION pCriticalSection,
   DWORD dwMilliseconds);
BOOL SleepConditionVariableSRW(
   PCONDITION_VARIABLE pConditionVariable,
   PSRWLOCK pSRWLock,
   DWORD dwMilliseconds,
   ULONG Flags);

VOID WakeConditionVariable(PCONDITION_VARIABLE ConditionVariable); //等待条件变量受信,能被其他线程唤醒
VOID WakeAllConditionVariable(PCONDITION_VARIABLE ConditionVariable); //状态,唤醒所有等待这个条件变量的线程

Event Objects

信号量(semaphore)

Win32 POSIX
创建 CreateSemaphore sem_init
等待 WaitForSingleObject sem_wait
释放 ReleaseMutex sem_post
试图等待 WaitForSingleObject sem_trywait
销毁 CloseHandle sem_destroy
C++ semaphore C++0x has no semaphores? How to synchronize threads?

Java Semaphore

最佳实践(boost:Best Practices)

推荐最简单而又能最大限度消除内存泄漏的准则是:总是使用有名字的智能指针变量握住new的结果。代码中每个new关键字出现的地方应该用这种形式: shared_ptr<T> p(new Y); 当然了,使用另一种智能指针代替上面的shared_ptr也是允许的,此处T和Y应该是同样的类型,或者Y也可以是带参数的构造函数。

遵守这个准则自然就表示没有显式使用delete语句,而try/catch结构就很少见到。

避免使用未命名的shared_ptr的临时量来节省代码。这种做法是危险的,如:

void f(shared_ptr<int>, int);
int g();

void ok()
{
   shared_ptr<int> p( new int(2) );
   f( p, g() );
}

void bad()
{
   f( shared_ptr<int>( new int(2) ), g() );
}

函数ok遵循了上述规范,而bad函数构造了一个临时shared_ptr,这就存在内存泄漏的可能性。因为函数参数表达式计算结果的顺序是未定的,有可能先new int(2)g(),此时如果g函数抛出异常那么shared_ptr构造函数永远得不到执行。更多详情可参考Herb Sutter's treatment (或者这里)。

更好地,上述异常安全问题也可以通过使用在boost/make_shared.hpp定义工厂函数(通过模板实现的)make_sharedallocate_shared来消除。工厂函数通过合并内存分配可以提高效率。

循环引用

Because the implementation uses reference counting, cycles of shared_ptr instances will not be reclaimed. For example, if main() holds a shared_ptr to A, which directly or indirectly holds a shared_ptr back to A, A's use count will be 2. Destruction of the original shared_ptr will leave A dangling with a use count of 1. Use weak_ptr to "break cycles."

原始指针获取注意点

  • 最一般误用从原始指针重复创建shared_ptr
    不当使用(sp1和sp2不知道有两份能自动管理内存,生命周期结束自动释放,对同一个被托管内存的引用,导致引用计数不正确,导致释放两次)
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

遵循推荐的最佳准则的格式,后一个对同一个被管理的裸奔指针的引用,sp2应该从第一个获取,应该改为如下:

shared_ptr<int> sp1(new int);
// auto sp1 = std::make_shared<int>(66); // nicer
shared_ptr<int> sp2 = sp2;

误用案例:

void f(X * p)
{
   shared_ptr<X> px(???);
}

可能原因:从指针p创建共享指针,在px离开作用于后,指针被默认删除器释放(析构),外部传进来的p指向的内容将无效。主要就是生命周期和管理,如果p不再用了还好,如果这个方法调用后还要用p则会有问题。

改进方案:如果可能,应该这样void f(shared_ptr<X> px);

由此引申this指针问题。

struct A {
 void func() {
   // only have "this" ptr ?
 }
};

int main() {
 A* a;
 std::shared_ptr<A> sp_a(a);
}

当A* a被shared_ptr托管的时候,如何在func获取自身的shared_ptr成了问题.
如果写成:

void func() {
 std::shared_ptr<A> local_sp_a(this);
 // do something with local_sp_a
}

又用a新生成了一个shared_ptr: local_sp_a, 这个在生命周期结束的时候可能将a直接释放掉.

这里就需要用enable_shared_from_this改写:

struct A : public enable_shared_from_this {
 void func() {
   std::shared_ptr<A> local_sp_a = shared_from_this();
   // do something with local_sp
 }
};

shared_from_this会从weak_ptr安全的生成一个自身的shared_ptr.

不能直接使用this创建的shared_ptr,否则如果func被作为回调函数传给某个其他object B, B肯定希望A的生命周期比自己长,如果A先被释放,那B调用func时就会爆炸。

多线程中使用注意点

线程安全

多线程时能够得到期望的结果

  • linux多线程服务端编程

依据Java Concurrency in Parctice,一个线程安全的class应当满足以下三个条件:

  • 多线程同时访问时,其表现出正确的行为。

  • 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织(interleaving)。

  • 调用端代码无须额外的同步或其他协调动作。 依据这个定义,C++标准库里的大多数class都不是线程安全的,包括std::string、std::vector、std::map等,因为这些class通常需要在外部加锁才能供多个线程同时访问。

  • Thread Safety

shared_ptr对象提供和内置类型同等级的线程安全级别。一个shared_ptr实例能够被多个线程同时“读”(仅通过const操作来访问)。不同的shared_ptr实例能够能够被多个线程(即使当那些实例是拷贝副本,底层共享着同样的引用计数(even when these instances are copies, and share the same reference count underneath.))同时“写入” (通过可变操作如operator=reset,析构被看作写操作)。
任何其他的同步访问会导致未定义行为。

对一个shared_ptr只有“读”,多个线程“读”就是线程安全;如果对这个shared_ptr还有“写”,则会导致未定义行为。推荐的正确做法是用mutex保护。

#include <stdio.h>
#include <stdlib.h>

#include <memory>
#include <mutex>

struct Foo {
	int n;
	Foo(int i) : n(i) {} // std::make_shared<Foo>(66);
};

std::mutex g_mutex;
std::shared_ptr<Foo> g_sp_foo;

void read()
{
	std::shared_ptr<Foo> localFoo;
	{
		std::lock_guard<std::mutex> lock(g_mutex); // RAII-style
		localFoo = g_sp_foo;
	}
	// Use localFoo to read or write to without locking again...
}

void write()
{
	//std::shared_ptr<Foo> newFoo = std::make_shared<Foo>(66);
	auto newFoo = std::shared_ptr<Foo>(new Foo(66));
	{
		std::lock_guard<std::mutex> lock(g_mutex); // RAII-style
		g_sp_foo = newFoo;
	}
	// Use newFoo to read or write to without locking again...
}

int main(int argc, const char *argv[])
{
	g_sp_foo = std::shared_ptr<Foo>(new Foo{ 22 });

	//std::this_thread::sleep_for(std::chrono::seconds(3));
	//getchar();
	return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment