C++ 并发入门:以 LeetCode 1114 为例

C++ 并发入门:以 LeetCode 1114 为例 题目 直接做题:1114 按序打印 解法 1. std::mutex 如果你对 c++ 11 略为熟悉的话,应该能够想到用 std::mutex 来解这道题,在函数构造时(主线程)对 std::mutex 进行 lock,然后在各个线程调用的函数中依次对 std::mutex 对象进行 unlock: class Foo { mutex mtx1, mtx2; public: Foo() { mtx1.lock(), mtx2.lock(); } void first(function<void()> printFirst) { printFirst(); mtx1.unlock(); } void second(function<void()> printSecond) { mtx1.lock(); printSecond(); mtx1.unlock(); mtx2.unlock(); } void third(function<void()> printThird) { mtx2.lock(); printThird(); mtx2.unlock(); } }; Mutex 即 mutual exclusion,是用来防止多个线程同时访问共享资源对象的机制,在同一时间只有一个线程可以拥有一个 mutex 对象,其他线程调用 std::mutex::lock 函数时会阻塞直到其获取锁资源。 ...

September 30, 2020 · 3 min

Effective C++ notes

Effective C++ 笔记 0 导言 1 构造函数 default 构造函数:可被调用而不带任何实参的构造函数,这样的构造函数要么没有参数,要么每个参数都带有默认值,例如 class Bar { public: // explicit Bar(); // 是 default 构造函数 // explicit Bar(int x = 0) // 不是 default 构造函数 explicit Bar(int x = 0, bool b = true); // 是 default 构造函数 private: int x; bool b; }; explicit 关键字:阻止执行隐式类型转换,其优点是禁止了编译器执行非预期的类型转换,例如 void Foo(Bar obj); // Foo 函数的参数是一个类型为 Bar 的对象 Bar obj_1; // 构造一个 Bar 类型的对象 Foo (obj_1); // 没问题,传递一个 Bar 类型的对象给 Foo 函数 Foo (Bar()); // 没问题,构造一个 Bar 类型的对象,并传递给 Foo 函数 Foo (2); // 如果 Bar 的构造函数没有被声明为 explicit,那么会调用 Bar 的构造函数构造一个成员变量 x = 2 的对象,也就是说发生了隐式类型转换;如果其构造函数被声明为 explicit,那么就不会构造出 Bar 类型的对象 copy 构造函数:用同类型的对象初始化新的对象,它定义了一个对象如何 pass by reference。 ...

September 24, 2020 · 6 min

C++ 单例模式的模板实现

C++ 单例模式的模板实现 单例模式是一种创建型的设计模式(creational design patterns),使用单例模式进行设计的类在程序中只拥有一个实例(single instance),这个类称为单例类,它会提供一个全局的访问入口(global access point),关于单例模式的讨论可以参考Singleton revisited;基于这两个特点,单例模式可以有以下几种实现: Meyer’s Singleton Scott Meyers 在 Effective C++ 的 Item 4: Make sure that objects are initialized before they’re used 里面提出了一种利用 C++ 的 static 关键字来实现的单例模式,这种实现非常简洁高效,它的特点是: 仅当程序第一次执行到 GetInstance 函数时,执行 instance 对象的初始化; 在 C++ 11 之后,被 static 修饰的变量可以保证是线程安全的; template<typename T> class Singleton { public: static T& GetInstance() { static T instance; return instance; } Singleton(T&&) = delete; Singleton(const T&) = delete; void operator= (const T&) = delete; protected: Singleton() = default; virtual ~Singleton() = default; }; 通过禁用单例类的 copy constructor,move constructor 和 operator= 可以防止类的唯一实例被拷贝或移动;不暴露单例类的 constructor 和 destructor 可以保证单例类不会通过其他途径被实例化,同时将两者定义为 protected 可以让其被子类继承并使用。 ...

September 10, 2020 · 2 min

并行计算入门

并行计算入门 1 概述 1.1 并行计算 高性能计算(High Performance Computing)是计算机科学中的一个领域,其目的可以概括为优化性能,它包括了缓存技术、数据结构和算法、IO 优化、指令重组(instruction reorganization)、编译器优化等; 并行计算(Parallel Computing)是高性能计算下的一个细分领域,其主要思想是将复杂问题分解成若干个部分,将每一个部分交给独立的处理器(计算资源)进行计算,以提高效率;针对不同的问题,并行计算需要专用的并行架构,架构既可以是专门设计的,含有多个处理器的单一硬件或超级计算机,也可以是以某种方式互连的若干台的独立计算机构成的集群;并没有一个统一的并行计算架构适用于每一个问题,如果使用了错误的架构,并行计算甚至会导致性能下降。 1.2 硬件架构 中央处理器(Central Processing Unit)的主要功能是解释计算机指令,它由控制单元(Control Unit)、算术逻辑单元(Arithmetic Logic Unit)、乱序控制单元(Out-of-Order Control Unit)、分支预测器(Branch Predictor)、数据缓存(Data Cache)等部件组成;CPU 被设计为可以快速地处理各种通用计算任务并最小化延迟,但在并发性(时钟频率)方面受到限制; 图形处理器(Graphics Processing Unit, GPU)是英伟达(NVIDIA)在 1999 年 8 月发布 NVIDIA GeForce 256 时提出的概念;现代 GPU 的模型设计可以概括为几个关键点: GPU 的设计目的是最大化吞吐量(Throughput) 能够将程序中数据可并行的部分从 CPU 转移到 GPU 能够使用尽可能多的线程进行并行计算 GPU 拥有的内核数量相较于 CPU 多得多,可以有数千个同时运行的内核执行大规模并行计算,因此在早期专门应用于图形数据的处理,但随着近十几年的发展,其强大的并行处理能力也使其可以处理非图形数据,尤其在深度学习领域非常受欢迎; 在制造工艺的限制下,芯片的密度和最大面积都是有限的(摩尔定律),因此芯片设计实际上是功能和元件数量的权衡;出于对通用性的要求,CPU 的芯片设计必须使用较多种类的原件以增加其功能,同时放弃部分具有复杂功能的元件数量,而 GPU 的芯片设计则是通过移除部分具有复杂功能的元件来换取更多的空间,并集成更多的基本功能元件; GPU 设备由多个流多处理器(Streaming Multiprocessor)的处理器集群(Processor Cluster)组成。每个流多处理器都关联一个控制单元 和 L1 Cache,这样的设计使得一个芯片可以同时支持上百个指令流的并行执行;通常一个流多处理器在与全局 GDDR-5 内存交换数据之前都会利用与之关联 L1 Cache 和 L2 Cache 来减少数据传输的延迟;而又因为 GPU 通常拥有足够大的计算量,使得其不需要与 CPU 一样非常频繁地从内存中获取数据,因此 GPU 的缓存层一般是小于 CPU 的。 ...

August 12, 2020 · 12 min

boost::typeIndex 的相关探究

boost::typeIndex 的相关探究 Effective Modern C++ 的 Item 4: Know how to view deduced types. 中提到了 Boost::typeindex 的使用,但并没有讲到其实现原理。 1. typeid 操作符 typeid 是 C++ 中的一个操作符,可以用于获取类型的信息,常常用在必须知道多态对象的动态类型,或是识别静态类型的地方。 我们可以写一个简单的 demo 用于获取对象类型相关的信息,需要包含 tepyinfo 头文件: #include <iostream> #include <typeinfo> using namespace std; class Foo {}; int main() { cout << "1: " << typeid(1).name() << endl; cout << "int: " << typeid(int).name() << endl; // 和 sizeof 操作符类似,typeid 也可以直接对数据类型(比如 int)进行操作 cout << "typeid: " << typeid(typeid(int)).name() << endl; cout << "typeid: " << typeid(const type_info &).name() << endl; const Foo *foo = new Foo(); cout << "foo: " << typeid(foo).name() << endl; cout << "*foo: " << typeid(*foo).name() << endl; cout << "Foo: " << typeid(Foo).name() << endl; } [joelzychen@DevCloud ~/typeid]$ g++ -std=c++11 -otypeid_test typeid_test.cpp [joelzychen@DevCloud ~/typeid]$ ./typeid_test 1: i int: i typeid: N10__cxxabiv123__fundamental_type_infoE typeid: St9type_info foo: PK3Foo *foo: 3Foo Foo: 3Foo std::type_info::name() 函数返回的字符串中,在 GCC 和 Clang 的实现里一般 i 代表 int,P 代表 pointer,K 代表 const,数字用于标识其后跟随了几个字符;我们可以将这段代码使用微软的 MSVC 编译运行,得到更加直观的输出: ...

July 31, 2020 · 6 min