0%

背景

高级语言经过编译器将源码转为机器指令运行,其中的运行顺序和代码中的顺序有很大差异,主要是下面三个原因:

  • 编译器重排
  • CPU 乱序执行
  • 存储器硬件设计,不同线程看到的顺序不一致。

c++ 中线程同步只有两种方式:

  • 原子变量进行同步
  • 锁(Mutex)

这里我们主要讨论原子变量的操作。

Memory Order

C++11 规定了六种不同的 memory order:

  • Relaxed
  • Consume
  • Acquire
  • Release
  • Acquire-Release
  • Sequential Consistent
    阅读全文 »

joindetach

  • join 或者 detach 只能调用一次

    当调用 join 或者 detach 之后会将持有的线程ID置为 0, 再次调用会抛异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    void
    thread::join()
    {
    int ec = EINVAL;
    if (!__libcpp_thread_isnull(&__t_))
    {
    ec = __libcpp_thread_join(&__t_);
    if (ec == 0)
    __t_ = _LIBCPP_NULL_THREAD;
    }

    if (ec)
    __throw_system_error(ec, "thread::join failed");
    }

    void
    thread::detach()
    {
    int ec = EINVAL;
    if (!__libcpp_thread_isnull(&__t_))
    {
    ec = __libcpp_thread_detach(&__t_);
    if (ec == 0)
    __t_ = _LIBCPP_NULL_THREAD;
    }

    if (ec)
    __throw_system_error(ec, "thread::detach failed");
    }
阅读全文 »

Grid

fr

1
2
3
grid-template-columns: 1fr, 1fr, 1fr, 1fr;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: 1fr, 1fr, 1fr, auto;

grid-column

1
2
3
4
grid-column: 1
grid-column: 1 / 4;
grid-column: 1 / span 3;
grid-column: 1 / -1;

文档主键

文档主键 _id

文档主键 _id 是每篇文档必备的字段,具有以下特性:

  • 文档主键的唯一性
  • 支持所有数据类型(数组除外)
  • 复合主键

对象主键 ObjectId

当我们不提供主键,MongoDB 自动为我们生成的默认对象主键 ObjectId

  • 默认的文档主键
  • 可以快速生成的 12 字节 id
  • 包含创建时间

ObjectId 默认值

1
2
test> ObjectId()
ObjectId("64eeed51b64b9d7e95e6b8ea")

手动设置 ObjectId 的值

1
2
test> ObjectId("123456789011123456789011")
ObjectId("123456789011123456789011")

提取 ObjectId 的创建时间

1
2
test> ObjectId("123456789011123456789011").getTimestamp()
ISODate("1979-09-05T22:51:36.000Z")
阅读全文 »

索引

  • 对文档部分内容进行排序的数据结构
  • 加快文档查询和文档排序的速度
  • 复合键索引只能支持前缀子查询

索引操作

创建索引

1
db.<collection>.createIndex(<keys>, <options>)
  • <keys> 文档指定了创建索引的字段
  • <options> 创建索引的参数和设定索引的特性

创建一个单键索引

1
db.accountWithIndex.createIndex({name: 1})

创建一个复合键索引

1
db.accountWithIndex.createIndex({name: 1, balance: -1})

创建一个多键索引,创建在数组字段上, 数组字段中的每一个元素都会在多键索引中创建一个键

1
db.accountWithIndex.createIndex({currency: 1})

列出集合中的索引

1
2
db.accountWithIndex.getIndexes()

阅读全文 »

macOS 上,动态链接器使用特定的路径变量来解析运行时的库位置。这些路径变量包括:绝对路径、 @executable_path@loader_path@rpath

绝对路径

对于安装在系统中共享位置的框架很有用,一般是 /Library/Frameworks/xxx/usr/lib/xxx, 但是查找嵌入在应用内部的动态库就很难使用,应用安装的位置都不固定,所以引出新的方式。

@executable path

@executable_path 是用于指代当前正在执行的程序或应用的路径。当你的应用程序或其动态库需要引用位于与可执行文件相同路径(或其子目录)下的其他动态库时,这会非常有用。

阅读全文 »

概述

libEvent, 一个事件通知库。有以下特点:

  • 事件驱动,高性能;
  • 轻量级,专注于网络;
  • 跨平台,支持 WindowsLinuxMac 等;
  • 支持多种 I/O 多路复用技术, epollpolldev/pollselectkqueue 等;
  • 支持 I/O ,定时器和信号等事件;

libevent接口分析

libevent 接口大概分为以下几类:

  • 环境配置和初始化
    • event_base_new
  • evutil socket 函数封装
    • evutil_make_socket_nonblocking
    • evutil_make_listen_socket_reuseable
      • set SO_REUSEADDR on Unix and does nothing on Windows
    • evutil_closesocket
  • 事件IO处理
    • event_new
  • 缓冲IO
    • bufferevent
  • 循环(Loop)
    • event_base_dispatch

Libevent API

libevent上下文创建

  • event_base *event_base_new(void)
  • event_base *event_base_new_with_config( const struct event_config *);
    • 配置参数
      • event_config *event_config_new(void);
      • void event_config_free(struct event_config * cfg);
  • event_reinit
    • int event_reinit(struct event_base *base); 调用 fork 之后可以正确工作
  • void event_base_free(struct event_base *); 释放event_base内部分配的空间及其本身对象的空间,不释放事件和socket和在回调函数中申请 的空间
  • event_config_set_flag
  • event_config_avoid_method(struct event_ config *cfg, const char *method);
  • event_config_require_features

event_base_config_flag

  • EVENT_BASE_FLAG_NOLOCK:

    • 不要为event_base 分配锁。设置这个选项可以为event_base 节省一点用于锁定和解锁的时间,但是让在多个线程中访问event_base 成为不安全的。
  • EVENT_BASE_FLAG_IGNORE_ENV

    • 选择使用的后端时,不要检测EVENT_*环境变量。使用这个标志需要三思:这会让用户更难 调试你的程序与libevent 的交互。
  • EVENT_BASE_FLAG_STARTUP_IOCP

    • 仅用于Windows,启用任何必需的IOCP 分发逻辑
    • iocp
      • event_config_set_num_cpus_hint
      • event_config_set_flag(cfg, EVENT_BASE_ FLAG_STARTUP_IOCP)
      • evthread_use_windows_threads();
  • EVENT_BASE_FLAG_NO_CACHE_TIME

    • 不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检 测。注意:这会消耗更多的CPU 时间。
  • EVENT_BASE_FLAG_EPOLL_USE_ CHANGELIST

    • epoll下有效,防止同一个fd多次激发事件,fd如果做复制会有bug
  • EVENT_BASE_FLAG_PRECISE_TIMER

    • 默认使用系统最快的记时机制,如果系统有较慢 且更精确的则采用

event_method_feature

  • EV_FEATURE_ET = 0x01
    • 边沿触发的后端
  • EV_FEATURE_O1 = 0x02,
    • 要求添加、删除单个事件,或者确定哪个事件激 活的操作是O(1)复杂度的后端
  • EV_FEATURE_FDS = 0x04,
    • 要求支持任意文件描述符,而不仅仅是套接字的 后端
  • EV_FEATURE_EARLY_CLOSE = 0x08
    • 检测连接关闭事件。您可以使用它来检测连接何时关闭,而不必从连接中读取所有挂起的数据。 并非所有后端都支持EV_CLOSED。允许您使用EV_CLOSED,而不需要读取所有挂起的数据。无法在所有内核版本上

事件Event处理

循环(loop)

缓冲 bufferevent

libevent http

select

select 实现多路复用的方式是,将已连接的 socket 都放到一个文件描述符集合,然后调用 select函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 函数原型

1
2
3
#include <sys/select.h>

int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
  • 返回值
    • 若有就绪描述符则为其数目,若超时则为 0,若出错则为 -1
  • 参数
    • maxfd: 待测试的描述符基数,它的值是待测试的最大描述符加 1
    • readfds:读描述符集合
    • writefds:写描述符集合
    • errorfds:异常描述符集合
    • timeout: 超时设置
阅读全文 »