CMake自动化
测试
CMake在命令行中使用ctest [<opetions>]命令执行测试,需要在构建完CMake项目后,在构建树中执行CTest。不过这种方式需要执行很多命令在多个工作目录间进行切换,为了简化流程,可以通过添加选项在构建时直接执行测试。
1234ctest --build-and-test <path-to-source> <path-to-build> --build-generator <generator> [<options>...] [--build-options <opts>...] [--test-command <command> [<args>...]]
构建-测试模式需要在ctest命令添加--build-and-test选项来开启。
在--test-command选项后可以添加测试用例需要传入的参数。
不过这并不会直接运行测试,除非在--test-command的后面提供ctest关键字:
123ctest --build-and-test p ...
使用CMake构建项目
使用目标
最简单的CMake的项目是创建单个的二进制可执行文件,例如一个"hello_world.cpp",而随着项目逐渐复杂,可能会有成百上千的文件被加入其中。这时就需要有某种方式可以对文件进行划分,将其按照功能分成不同的单元,其中一个单元可能会依赖其他的单元,这样的单元就是目标。
概念
目标是个强大的概念,它极大地简化了项目的构建。
CMake中可以通过以下指令来创建目标:
add_executable()
add_library()
add_custom_target()
分别对应了创建可执行目标,库目标和自定义的目标,其中自定义的目标意思是允许你可以执行指定的命令行,在不检查输出是否为最新的情况下执行。
12add_custom_target(clean_files COMMAND find . -name "*.txt" -type f -delete)
比如定义一个这样的自定义目标,将搜索所有扩展名为.txt的文件,并删除它们。不过自定义目标只有被添加到依赖关系中才会被构建。
CMake有一个很好的模块可以生成graph ...
CMake语法
CMake语法
注释
单行注释使用#,多行注释使用[=[开始,使用]=]结束,其中等号可以是任意数量的,但是开始和结束的等号数量必须一致。
指令
执行指令是CMake列表文件的基本功能,提供它的名称,后面跟着小括号,在小括号中可以包含一个以空格为分隔的参数列表,例如message("hello" world)。
指令名不区分大小写,但一般约定使用小写并以下划线连接。
指令参数
在底层,CMake识别的唯一数据类型只有字符串,但是静态的字符串并不好用,所以CMake提供了三种类型的参数。
类型
说明
方括号参数
方括号参数不会求值,只用于将多行字符串作为单个参数传递给命令,和多行注释一样,使用两层方括号标识。
引号参数
类似于C++中的常规字符串,同时可以支持将变量引用包装进字符串中,比如${target}这种。
非引号参数
和引号参数差不多,都可以当作常规字符串使用,但是要小心使用;,因为分号会被当作参数间的分隔符。
变量
CMake中有三类变量——普通变量、缓存变量和环境变量。
变量名区分大小写,可以包括任何字符
...
持久化
持久化
I/O设备
系统架构
基本上可以分为三层,从上到下的速度依次变慢,最上层的内存总线是不对外暴露的,只用于内存和CPU间的传输,其速度也是最快的,其次是PCI类似的通用I/O总线,所有的外围设备都会通过这条总线与CPU进行信息传输,其中显卡直连在这条线路上,而其他设备还需要一条最慢的外设总线来作为中转,这条外设总线几乎可以支持所有常用的外设协议,包括SATA、USB等。
DMA
DMA,全称为Direct Memory Access,是一个用于优化I/O设备读写的硬件设备。
通常在一个进程中,CPU在将数据从内存搬运到I/O设备的过程是需要占用CPU时间的,而这部分由于硬件架构的限制,往往较慢,这就拖慢了CPU的执行效率。
CPU可以将数据的内存地址和对应的I/O设备信息发送给DMA,由DMA来不经过CPU直接将数据搬入或搬出I/O设备,在执行这个操作的进程就可以放弃当前时间片,等待数据传输完成就好了,CPU可以去执行其他真正需要CPU参与运算的操作,这就提高了CPU的执行效率。
磁盘
磁盘是持久化存储数据的I/O设备。磁盘上的最小单位通常是512字节,又称为一个扇区,磁盘就是 ...
高级线程管理
高级线程管理
C++提供了std::thread对线程进行管理,但面对复杂的任务情况,往往有些捉襟见肘,我们希望可以对线程的整个生命周期进行管理,而不是放任其直到执行完成,理想情况下,能够将代码划分为更小的块,再通过并发执行。
另外,有时候我们还希望在线程启动后可以有方法“干预”其执行,比如某些情况下让其停止或者等待某个条件的达成等等。
这些更具体的要求使得我们不得不去使用更高级的线程管理方式,而不能仅仅使用C++提供的线程封装对象。
线程池
线程池的基本思想就是为了解决上述提到的第一个问题,主要的矛盾是并发任务数量和硬件线程数量有限之间的矛盾,最理想和最简单的方法当然是给每个并发任务一个线程,然后让它们在需要的时候执行即可,但硬件的线程数量总是有限的,而且也不是每个任务都是必要要同时执行的。
正如一个公司的职员需要出差,有的出差时间长,有的出差时间短,有的一年不一定出差一次,有的可能一个月要出差两三次,最好的方法是每个人都配辆车,但这个开销太大了也不现实,那么最经济实惠的方式就是公司分配几辆出差专用的车,谁需要出差就开走,出完差再换回来就行了。
线程池雏形
可以简单的实现一个线程池雏 ...
什么是MVCC?
什么是MVCC?
MVCC,全称为Multi-Version Concurrency Control,即多版本并发控制。主要的目的是为了提高数据库的性能,更好地处理读写冲突,即使出现了读写冲突也可以通过不加锁的方式来解决。
多版本是指数据库中的数据存在多个版本,
当前读和快照读
在介绍MVCC前,需要先了解当前读和快照读两个概念。
当前读
在当前读中,事务只能读取已经提交的数据。这意味着如果一个事务正在对数据行做出修改,而另一个事务也要读取这一行,后者会等待前者完成修改。因此,当前读会提供更加一致的视图。
MySQL中提供了两种当前读的实现:
一致性读
在可重复读的隔离等级下,MySQL使用一致性读来实现当前都。在事务开始时,会创建一个一致性视图,这个视图是事务开始时刻数据库的快照,在事务执行期间,无论其他事务对数据做了何种修改,当前事务都只会从这个一致性视图中读取数据,保证了读取的一致性。
锁定读
当使用锁定读时,会对数据上一把共享锁或者排他锁,从而保证了数据的一致性。
锁定读适用于需要严格控制并发的场景,而且加锁带来的性能开销较大。
当前读会使用到的语法
sele ...
无锁并发数据结构
无锁并发数据结构
互斥量是一个强大的工具,可以在并发情况下保护数据,防止数据竞争的发生,但同时,使用互斥量难免会影响并发的性能。如果能写一个无锁安全的并发数据结构,就能更好地发挥并发。
原子量实现的简单自旋锁
123456789101112131415class SpinLock {public: SpinLock() : m_flag(ATOMIC_FLAG_INIT) { } void lock() { while(m_flag.test_and_set(memory_order_acquire)); } void unlock() { m_flag.clear(memory_order_release); }private: atomic_flag m_flag;};
通过原子量的不断自循环来实现锁定,直到有其他线程调用了unlock()后,才会退出循环,这种不断自选来维持线程的锁定的操作就被形象地称为自旋锁。
概念和意义
通常情况下,可 ...
内存模型和原子操作
内存模型和原子操作
对象和内存位置
C++中,对象仅是对数据块的声明,C++标准定义类对象是“存储区域”,但对象可以将自己的特性赋予其他对象。
位域
C/C++中可以指定数据在内存中的存储大小(如果有必要的话),最常见的是为了压缩数据的大小。使用位域需要遵循以下的规则:
显式指定了位域大小的变量,如果总的位域之和不超过一个最小存储长度的话,一般是8bit,就会被放到同一个位置上(比如在同一个字节中),如果放不下的话,第二个变量会放到下一个位置,而第一个变量所在位置剩下的空间不会被使用。
12345struct S { int a : 2 { 1 }; int b : 3 { 3 }; int c : 3 { 4 };};
比如这样的一个结构,其中a占了2bit,b占了3bit,c占了4bit。如果打印S的大小和其中变量的值,能得到结果
1234sizeof(S): 4s.a = 1;s.b = 3;s.c = -4;
很神奇,我们定义了3个整型变量,但整个结构的大小还是只有一个in ...
虚拟化之内存
虚拟化之内存
早期的操作系统实际上就是一个库,存放在物理内存的0KB到64KB的位置,然后其他的进程将使用剩下的内存,这时的内存对应的就是物理内存上的地址,完全没有抽象的概念。后来随着程序越来越多,人们开始共享机器,并且程序间不应该存在影响,安全显得越发重要。这时候人们就开始考虑程序不应该直接获取物理内存上的位置信息,而是应该把程序和物理内存隔离一下。
地址空间
操作系统提供了一个简单易用的物理内存抽象,叫做地址空间。一个进程的地址空间包括所运行程序的全部内存状态,比如程序代码,栈、堆,用户管理的内存等等。
例如一块512KB的内存,0到64KB被分配给了操作系统,然后从128KB的位置到192KB的位置被分配给了进程A,从192KB到256KB的位置被分配给了进程B,从320KB到384KB的位置被分配给了进程C,如果每个进程都知道自己在物理内存中的确切位置,这就是没有抽象内存时的做法。现在我们将内存抽象成了地址空间,那么每个进程实际上都认为自己在独占整个内存的,认为它是从内存的0位置开始加载的,而操作系统在硬件支持下(一个叫做MMU的内存管理单元,负责将虚拟地址和对应的物理地址进行 ...
SQL之事务
SQL事务
什么是事务
在数据库中,我们将一条SQL语句称为一次基本的操作。将若干条SQL语句“打包”在一起,共同执行一个完整的任务,这就是事务。
事务(Transaction)由一次或者多次基本操作构成,或者说,事务由一条或者多条 SQL 语句构成。
reference: https://zhuanlan.zhihu.com/p/605751518
事务总是针对于多并发来说的,因为只有一个客户端或一个线程的情况下,对于数据库的操作都是线性的(如果不搞些骚操作的话),这时候的每一个SQL语句都是按照顺序执行的,也就无所谓事务不事务的了。
但是在多并发的场景下就大大不同了,假设这样的场景:你在一张表中存了一些种子链接,正欲找出来做一番学术研究,正好你的朋友也在操作这张表,一不小心把表清空了,你喝着可乐唱着歌,突然就找不到资源了,这可太难受了。
这就需要一种机制来保证不会出现这种情况。事务的概念正是为了正义的资源不被邪恶篡改而提出的(bushi
众所周知,事务具有以下4个属性,并称ACID:
原子性,一个事务中的操作要么全部成功,要么全部失败。
一致性,在一个事务的开始前和结束后,数 ...