Tip of the Week 3 字符串拼接,operator+ vs StrCat()
当审查代码的人对程序员说“别用字符串拼接操作,太低效了”,程序员尝尝会一脸吃惊:std::string::operator+怎么会低效呢?难道这不是很难出错的方式吗?
事实证明,这种低效没有那么简单明了。在实践中,下面两段代码执行时间相差不多:
123456std::string foo = LongString1();std::string bar = LongString2();std::string foobar = foo + bar;std::string foo = LongString1();std::string bar = LongString2();std::string foobar = absl::StrCat(foo, bar);
然而,同样的结论却不适用于下面两段代码:
123456789std::string foo = LongString1();std::string bar = LongString2();std::string baz = LongString3();string foobar = foo + bar + baz;std::str ...
C++线程同步之互斥锁
进行多线程编程,如果多个线程需要对同一块内存进行操作,比如:同时读、同时写、同时读写对于后两种情况来说,如果不做任何的认为干涉就会出现各种各样的错误数据。这时因为线程在运行的时候需要先得到CPU时间片,时间片用完之后需要放弃已获得的CPU资源,就这样线程频繁地在就绪态和运行态之间切换,更复杂一点还可以在就绪态、运行态、挂起态之间切换,这样就会导致线程的执行顺序并不是有序的,而是随机的混乱的,就如同下图中的这个例子一样,理想很丰满,现实很残酷。
解决多线程数据混乱的方案就行进行线程同步,最常用的就是互斥锁,在C++11中一共提供了四种互斥锁:
**std::mutex**: 独占的互斥锁,不能递归使用
std::timed_mutex: 带超时的独占互斥锁,不能递归使用
**std::recursive_mutex**: 递归互斥锁,不带超时功能
**std::recursive_timed_mutex**: 带超时的递归互斥锁
互斥锁在有些资料中也被称之为互斥量,两者是一个东西。
1. std::mutex不论是在C还是C++中,进行线程同步的处理流程基本上是一致的,C++中的 ...
call_once
在某些特定情况下,某些函数只能在多线程环境下调用一次,比如:要初始化某个对象,而这个对象只能被初始化一次,就可以使用std::call_once()来保证函数在多线程环境下只能被调用一次。使用call_once()的时候,需要一个once_flag()作为call_once()的传入参数,该函数的原型如下:
123// 定义于头文件 <mutex>template< class Callable, class... Args >void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
flag: once_flag类型的对象,要保证这个对象能够被多个线程同事访问到
f: 回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数
arg: 作为实参传递给回调函数
多线程操作过程中,std::call_once()内部的回调函数只会被执行一次,示例代码如下:
1234567891011121314151617181920212223242 ...
Tip of the Week 1 string_view
参考链接:abseil.io/tips/1
什么是string_view,为什么需要string_view当你在把一个字符串(或者字符串常量)作为一个函数参数的时候,通常有三种选择:前面两种你肯定是知道的,剩下的你可能没有意识到:
123456789// C Conventionvoid TakesCharStar(const char* s);// Old Standard C++ conventionvoid TakesString(const std::string& s);// string_view C++ conventionsvoid TakesStringView(absl::string_view s); // Abseilvoid TakesStringView(std::string_view s); // C++17
当调用者传入字符串格式的参数来调用函数,前面两种方式是可以正常工作的,但是在这个过程中是如何做转化的呢?无论是从const char* 到 std::string还是从std::string到const char*?
调用者 ...
命名空间-this_thread
在C++11中不仅添加了线程类,还添加了一个关于线程的命名空间std::this_thread,在这个命名空间中提供了四个公共的成员函数,通过这些成员函数就可以对当前线程进行相关的操作了。
1. get_id调用命名空间std::this_thread中的get_id()方法可以得到当前线程的线程ID,函数原型如下:
1thread::id get_id() noexcept;
关于函数使用对应的示例的代码如下:
123456789101112131415#include <iostream>#include <thread>using namespace std;void func(){ cout << "子线程: " << this_thread::get_id() << endl;}int main(){ cout << "主线程: " << this_thread::get_id() << endl; ...
C++线程的使用
C++11之前,C++语言没有对并发编程提供语言级别的支持,这使得我们在编写可移植的并发程序时,存在诸多的不便。现在C++11中增加了线程以及线程先关的类,很方便的支持了并发编程,使得编写的多线程程序的可移植性得到了很大的提高。
C++11中提供的线程类叫做std::thread,基于这个类创建一个新的线程非常的简单,只需要提供线程函数或者函数对象即可,并且可以同时指定线程函数的参数。我们首先来了解一下这个类提供的一些常用API:
1. 构造函数123456789// ①thread() noexcept;// ②thread( thread&& other ) noexcept;// ③template< class Function, class... Args >explicit thread( Function&& f, Args&&... args );// ④thread( const thread& ) = delete;
构造函数1:默认构造函数,构造一个线程对象,在这个线程中不执行任何处理动作
构 ...
处理日期和时间的chrono库
C++11中提供了日期和时间相关的库chrono,通过chrono库可以很方便的处理日期和时间,为程序的开发提供了便利。chrono库主要包含三种类型的库:**时间间隔duration、时钟clocks、时间点time point**。
1. 时间间隔duration1.1 常用类成员duration表示一段时间间隔,用来记录时间长度,可以表示几秒、几分钟、几个小时的时间间隔。duration的原型如下:
12345//定义于头文件 <chrono>template< class Rep, class Period = std::ratio<1>> class duration;
**Rep**:这是一个数值类型,表示时钟数(周期)的类型(默认为整形)。若Rep是浮点数,则duration能使用小数描述时钟周期的数目。
**Period**:表示时钟的周期,原型如下: 12345// 定义于头文件 <ratio>template< std::intmax_t Num, std::intmax_t Denom = ...
Android匿名共享内存ashmem
1. 进程间通信之共享内存Android匿名共享内存—ashmem,其本质是一种共享内存实现方案,而共享内存是操作系统实现进程间通信(IPC)的一种手段。众所周知,binder驱动是Android系统运用最广泛的一种IPC,那么为什么还需要ashmem呢?我么将在文章的最后给出答案,在此之前,我们将其作为一种共享内存机制来说明其实现原理。
先来看一下共享内存的原理。
1.1 什么是虚拟内存虚拟内存—vm,不是一块实际存在的内存,为了环节物理内存不够用的问题,往往从磁盘空间中开辟一块空间作为物理内存的扩展,这块开辟的磁盘空间称之为交换区—swap。
但是我们都知道,CPU是不能直接访问磁盘空间的,所有的数据都必须先从磁盘空间读入物理内存之后才能被CPU访问,所以swap的工作方式并不是简单的当物理内存用完之后直接从交换区读取数据,而是有一个换出(swap out)换入(swap in)的过程。
具体来说,根据局部访问原理,我们总是认为物理内存中存储的总是最常访问的数据,所以当我们要访问数据时,大多数时候可以直接从物理内存中访问到。但是,当访问数据不在物理内存中时,内核需要将数据从磁盘 ...
bazel构建C++工程
1. bazel介绍Bazel是一个开源的构建和测试工具,类似于Make、Maven和Gradel。Bazel支持多种语言的项目,并未多种平台构建输出。Bazel支持跨多个存储库和大量用户的大型代码库。
2. bazel安装bazel安装有两种方法,一种是通过安装,另一个是通过下载安装包本地安装。ubuntu系统建议使用第一种安装方式。
第一步:添加bazel分发url作为包源
123sudo apt install curl gnupgcurl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
第二步:安装或者更新bazel
1sudo apt update && sudo apt install bazel
如果已经安装 ...
巧妙的kfifo
在编写一个字符驱动的时候想用一个Ring Buffer的结构来做数据的读取/写入,自己去设计了一个RingBuffer的数据结构,然后使用一个read/write的指针来指向读取和写入buffer的地址,由于是ring buffer所以会考虑到写入超过size的时候回到起始地址的操作。
写完之后偶然发现Linux Kernel中有现成的kfifo实现,拜读了一下代码之后,发现自己的代码太low了,原来Linux Kernel的kfifo才是大神级别的代码,简洁且美观,没有一行多余的代码。
下面我们来分析一下Linux Kernel的kfifo是如何实现和工作的。
kfifo概述kfifo是内核里面的一个First In First Out数据结构,它采用唤醒循环队列的数据结构来实现,但是它提供的是一个无边界的字节流,最重要的一点是,它使用并行无锁编程技术,也就是当它用于只有一个入队线程和一个出队线程的场景时,两个线程可以并行操作,不需要加锁就可以保证kfifo的线程安全。
OK,既然那么多特点,先来看数据结构:
1234567struct kfifo { unsig ...














