CMake快速上手指南

CMake是一个跨平台的自动化构建系统,它使用配置文件(CMakeLists.txt)来生成标准的构建文件,如Unix的Makefile或Windows的Visual Studio工程文件。CMake旨在支持多平台源代码编辑和管理,并且可以用于管理复杂项目和大型代码库的构建过程。

CMake的主要特点包括:

  1. 跨平台: 支持在多种操作系统上构建项目,包括Windows、Linux、macOS等。

  2. 生成构建系统: 根据CMakeLists.txt文件生成适用于不同平台的构建系统或IDE项目文件。

  3. 可扩展: 允许用户通过编写自己的CMake模块和脚本来扩展其功能。

  4. 查找依赖: 能够自动查找并配置项目所需的外部库和依赖项。

  5. 配置选项: 提供丰富的配置选项,允许用户自定义构建类型、编译选项等。

  6. 安装规则: 支持定义安装规则,方便软件的打包和分发。

  7. 集成测试: 支持集成测试,确保代码质量。

  8. 社区支持: 拥有一个活跃的社区和丰富的在线资源,包括文档、教程和论坛。

  9. 适用于大型项目: 特别适合于大型项目和多语言支持的项目。

CMake通过提供一套统一的构建和配置接口,简化了在不同平台上编译和构建项目的复杂性,是许多开源项目和商业软件所采用的构建工具之一。

阅读更多

Go语言常见使用错误总结

在学习和使用 Go 语言的过程中,我们不可避免地会遇到一些陷阱和常见的使用错误。这些建议性错误或潜在的问题,有时可能会在代码中悄悄滋生,直到某一天给你带来难以察觉的 bug。为了帮助大家更好地理解和规避这些陷阱,我整理了一些 Go 语言中常见的使用错误,这些建议或许能成为你编写更健壮、可维护代码的助力。

本篇博客旨在提醒读者注意一些在 Go 语言中易犯的错误,不仅包括语法和语义级别的问题,还包括一些最佳实践和规范。通过深入理解这些错误,我们可以更好地规避潜在的风险,写出更高效、更稳定的 Go 代码。

在我们开始探索这些错误之前,让我们一同回顾一下“在错误中学习,不断成长”的理念。编程世界中,错误不是失败的代名词,而是成长的机会。当我们深入了解常见错误时,我们更能够逐步提升自己的编程技能,写出更加健壮的代码。

愿这篇博客能够帮助你更好地使用 Go 语言,避免一些不必要的困扰。让我们开始我们的探索之旅,一同领略 Go 语言的优雅之美,并在编程的路上越走越远。

Happy coding! 🚀

切片相关

对切片并发地进行append操作

append操作是并发不安全的,在使用过程中,需要特别注意。下面代码中是有问题的:

1
2
3
4
5
6
7
func append_to_slice(s []int, i int) {
append(s, i)
}

var slices = []int{1,2, 3}
go append_to_slice(slices, 4)
go append_to_slice(slices, 5)

解决办法之一是我们可以使用sync.Mutex进行加锁处理。

阅读更多

【翻译】SSL和SSL证书初学者指南

原文: SSL and SSL Certificates Explained For Beginners

安全套接字层 (SSL,全称Secure Sockets Layer)传输层安全 (TLS,全称Transport Layer security) 是通过计算机网络或链接提供安全通信的协议。它们通常用于网页浏览和电子邮件。在本教程中,我们将了解学习到:

  • TLS 和 SSL
  • 公钥和私钥
  • 为什么我们需要证书以及它们的作用
  • 如何获取数字证书并了解不同的常见证书类型

什么是 TLS?

TLS 基于 SSL,并作为替代方案而开发以应对 SSLv3 中的已知漏洞。SSL 是常用术语,我们说的 SSL 通常指的就是 TLS。

SSL/TLS 提供安全保障

SSL/TLS 提供数据加密、数据完整性和身份验证功能。这意味着当使用 SSL/TLS 时,你可以确保:

  • 没有人读过你的消息
  • 没有人篡改过你的消息
  • 你正在与预期的人(服务器)通信

在两方之间发送消息时,你需要解决两个问题。

  • 你怎么知道没有人读过这条消息?
  • 你怎么知道没有人篡改过该消息?

这些问题的解决办法是:

  • 对其进行加密(Encrypt it) : 这会使内容无法读取,因此对于查看该消息的任何人来说,它只是乱码。
  • 签名(Sign it) : 这可以让收件人确信是你发送的邮件,并且邮件未被篡改。

这两个过程都需要使用密钥。这些密钥只是数字(常见的是 128 位),然后使用特定方法(通常称为算法)与消息组合,例如RSA,对消息进行加密或签名。

阅读更多

【翻译】如何在微控制器中运行Rust?

原文:Running Rust on Microcontrollers

概览

Rust 是一个相当新的编程语言(它诞生于20101年),但在开发嵌入式固件方面显示出巨大的潜力。它首先被设计为一种系统编程语言,这使得它特别适合用于微控制器。它试图通过实现一个强大的所有权模型(可以消除整个错误类的发生)来改进 C/C++ 的一些最大缺点,这对固件也非常适用。

截至2022年,CC++ 编程语言仍然是嵌入式固件的事实标准。然而 Rust 在固件中的角色看起来很光明。Rust 对固件的支持并不是后面才考虑到,而是一开始就考虑支持。 为此,Rust 专门有官方的 嵌入式设备工作组 和 介绍如何使用 Rust 进行嵌入式开发的 嵌入式Rust之书。下图就是Rust嵌入式设备工作组logo2

Rust嵌入式设备工作组logo

本篇文章旨在探索在微控制器(这里指的是低级嵌入式固件,而不是在 Linux 等主机环境上运行)上运行 Rust,涵盖以下内容:

  • 语言特性
  • 架构支持
  • MCU家族支持
  • IDE, 编码 和 编码体验
  • 实时操作系统
  • Rust缺点

阅读更多

y % x = y & (x - 1)的数学证明

在基于哈希表实现的Map中一个常用技巧就是将哈希桶的数量设置为2的n次方,也就是2n2^n,此后通过取余操作定位key所在桶的位置可以转换成与运算。之所以将取余运算改成与运算,一方面这两者计算的结果是一样的,另外一方面是因为与运算具有更好的性能,因为与运算指令周期是小于取余运算的。Java中的 HashMap 和Go中 map 都使用到这个技巧。

基于哈希表实现的Map中的取余运算转换成与运算的技巧,用数学语言来表达:

对于正整数x, y,如果x为2的n次方,n为正整数,那么y  %  x=y  &  (x1)y\;\%\;x = y \;\&\; (x-1) 表达式是成立的。

阅读更多

Golang调度机制浅析

最近在部门中做了一次技术分享,现将分享内容总结成博文发布出来,内容有删改。

Golang以并发见长,支持成千上万个协程调度。Golang中协程称为Goroutine,它是Go runtime调度中的最小执行单元,Goroutine的创建、管理、调度运行的机制采用的GMP模型。本次分享介绍的就是Golang调度机制的GMP模型。

并行 vs 并发

并行(Parallelism) 指的是一个CPU时间片内可以同时做多件事情。并行强调的是某一时间点内能够同时处理多件事情,并行需要多核CPU提供支持。并行是并发的子集

并发(Concurrency) 指的是是一种同时处理许多事情的能力,并行强调是某一时间段内能够同时处理多件事情

阅读更多

并发编程系列:谈谈锁的实现机制

最近读了《Operating Systems: Three Easy Pieces》一书,全书主要围绕虚拟化、并发和持久化这三个主题展开,其中并发部分中介绍锁的章节,行文风趣幽默,写得非常精彩。文中介绍了多种实现锁的方案,以及各种锁的适用场景和优缺点。本文基于该书中锁章节,以一个gopher的角度去分享、拓展书中介绍的锁,并尽量使用Go实现书中介绍的几款自旋锁。

锁的基本思想

锁(lock)的目的是给临界区(Critical Section)加上一层保护,以保证临界区中代码能够像单条原子指令一样执行。临界区指的是一个访问共享资源的程序片段,比如对全局变量的访问、更新。在Linux系统中保护临界区的机制除了锁之外,还有信号量,屏障,RCU等手段。

锁本质是一个变量,我们通过lock()和unlock()这两个语义函数来操作锁变量。当线程准备进入临界区时候,会调用lock()尝试获取锁,当该锁状态是未上锁状态时候,线程会成功获取到锁,从而进入到临界区,如果此时其他线程尝试获取锁而进入临界区,会阻塞或者自旋。获取锁并进入临界区的线程称为锁的持有者,当锁持有者退出临界区时候,调用unlock()来释放锁,那么阻塞等待的其他线程继续开始竞争这个锁。下面是获取锁和释放锁的代码示例:

1
2
3
4
lock_t mutex;
lock(&mutex); // 加锁
balance = balance + 1; // 临界区资源
unlock(&mutex); // 释放锁

阅读更多

并发编程系列:开篇

本文是并发编程系列博文的开篇。笔者将打算写一系列并发编程相关的博文。下面是具体索引,方便大家查看:

  • 并发编程系列:谈谈锁的实现机制

    • 介绍锁的几种实现机制,自旋锁,两阶段锁,读写锁,混合空间锁等知识
  • 并发编程系列:死锁

    • 介绍死锁的定义,死锁产生的原因,以及几种解决死锁的方案(活锁、银行家算法、乐光并发控制等)。
    • 以哲学家就餐问题为示例实现几种避免死锁的方案。
  • 并发编程系列:无锁编程之栈

    • 介绍CAS原子操作,并以此实现无锁并发安全的栈。介绍CAS存在的ABA问题,以及解决办法。
    • 介绍如何使用指数退避栈解决栈顶争用严重问题
  • 并发编程系列:无锁编程之队列

    • 介绍有界部分队列,无界完全队列这两种类型队列。
  • 并发编程系列:无锁编程之ring buffer

    • 介绍ring buffer, 以及MPMC、MPSC、SPMC、SPSC等模型的ring buffer无锁版本实现
  • 并发编程系列:无锁编程之优先级队列

    • 介绍优先级队列概念
    • 基于锁实现和无锁实现的优先级队列
  • 并发编程系列:缓存一致性协议、内存屏障

CGO使用指南

Go 提供一个名为C的伪包(pseudo-package)用来与 C/C++ 语言进行交互操作,这种Go语言与C语言交互的机制叫做 CGO。通过 CGO 我们可以在 Go 语言中调用 C/C++ 代码,也可以在 C/C++ 代码中调用Go语言。CGO 本质就是 Go 实现的 FFI(全称为Foreign function interface,用来描述一种编程语言编写的程序可以调用另一种编程语言编写的服务的机制)解决方案。

当 Go 代码中加入import C语句来导入C这个不存在的包时候,会启动 CGO 特性。此后在 Go 代码中我们可以使用C.前缀来引用C语言中的变量、类型,函数等。启动 CGO 特性时候,需要确保环境变量 CGO_ENABLED 值是1,我们可以通过go env CGO_ENABLED查看该环境变量的值,通过go env -w CGO_ENABLED=1用来设置该环境变量值。

序言

我们可以给import C语句添加注释,在注释中可以引入C的头文件,以及定义和声明函数和变量,此后我们可以在 Go 代码中引用这些函数和变量。这种注释称为 序言(preamble) 。需要注意的是 序言和import C语句之间不能有换行,序言中的静态变量是不能被Go代码引用的,而静态函数是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

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

static void myprint(char* s) {
printf("%s\n", s);
}
*/
import "C"
import "unsafe"

func main() {
cs := C.CString("hello world")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}

阅读更多

【译文】Go 开发中我一定会用到的 7 种代码模式

原文:7 Code Patterns in Go I Can’t Live Without

代码模式使你的程序更可靠、更高效,并使你的工作和生活更轻松

我已经为开发EDR解决方案工作了7年。这意味着我必须编写具有弹性和高效性的长时间运行的系统软件。我在这项工作中大量使用 Go,我想分享一些最重要的代码模式,你可以依靠这些模式你的程序更加可靠(reliable)和高效(efficient)。

使用Map实现Set

我们经常需要检查某些对象是否存在。例如,我们可能想检查之前是否访问过某个文件或者URL。在这些情况下,我们可以使用map[string]struct{}。如下所示:

使用空结构 struct{} 意味着我们不希望Map的值占用任何空间。有些人会使用 map[string]bool,但基准测试表明 map[string]struct{} 在内存和时间上都表现得更好。相关基准测试可以查看这里

阅读更多