0%

  1. 框架层调用camera_module_t->common.open(),之后HAL返回一个hardware_device_t结构。

    Framework calls camera_module_t->common.open(), which returns a hardware_device_t structure.

  2. 框架层检查hardware_device_t->version,并根据相机硬件设备的版本创建相应的处理程序。如果版本为CAMERA_DEVICE_API_VERSION_3_0,设备实例将会被转化为camera3_device_t

    Framework inspects the hardware_device_t->version field, and instantiates the appropriate handler for that version of the camera hardware device. In case the version is CAMERA_DEVICE_API_VERSION_3_0, the device is cast to a camera3_device_t.

  3. 框架层调用camera3_device_t->ops->initialize()并将框架层回调函数指针传入。该函数只会在open()函数被调用后和在ops结构的任何其他函数被调用前的时间间隔中被调用一次。

    Framework calls camera3_device_t->ops->initialize() with the framework callback function pointers. This will only be called this one time after open(), before any other functions in the ops structure are called.

  4. 框架层调用camera3_device_t->ops->configure_streams(),将一个输入/输出流列表传递到HAL设备中。

    The framework calls camera3_device_t->ops->configure_streams() with a list of input/output streams to the HAL device.

  5. 根据设备版本进行不同的处理

    版本低于或等于CAMERA_DEVICE_API_VERSION_3_1时,框架层分配图形缓冲区并为configure_streams中的至少一个输出流调用camera3_device_t->ops->register_stream_buffers()。相同的流只会被注册一次。

    <= CAMERA_DEVICE_API_VERSION_3_1: The framework allocates gralloc buffers and calls camera3_device_t->ops->register_stream_buffers() for at least one of the output streams listed in configure_streams. The same stream is registered only once.

    版本低于或等于CAMERA_DEVICE_API_VERSION_3_2时,camera3_device_t->ops->register_stream_buffers()不会被调用,其必须为NULL

    CAMERA_DEVICE_API_VERSION_3_2:camera3_device_t->ops->register_stream_buffers() is not called and must be NULL.

  6. 框架层调用camera3_device_t->ops->construct_default_request_settings()请求一些使用用例的默认设置。这可能发生在步骤3之后的任何时间。

    The framework requests default settings for some number of use cases with calls to camera3_device_t->ops->construct_default_request_settings(). This may occur any time after step 3.

  7. 框架层构造并向HAL发送第一个捕获请求。该请求携带着基于默认设置集合中的某一个默认设置的设置项,并且关联着不少于一个之前框架层完成注册的输出流。请求被camera3_device_t->ops->process_capture_request()方法传送到HAL。HAL将会阻塞该方法的返回,直到下一个请求将被发送。

    The framework constructs and sends the first capture request to the HAL, with settings based on one of the sets of default settings, and with at least one output stream, which has been registered earlier by the framework. This is sent to the HAL with camera3_device_t->ops->process_capture_request(). The HAL must block the return of this call until it is ready for the next request to be sent.

    版本为CAMERA_DEVICE_API_VERSION_3_2或更高时,在camera3_capture_request_t中的camera3_stream_buffer_t数组中的buffer_handle_t可能是新的,HAL从未在任何新的请求中见过。

    >= CAMERA_DEVICE_API_VERSION_3_2: The buffer_handle_t provided in the camera3_stream_buffer_t array in the camera3_capture_request_t may be new and never-before-seen by the HAL on any given new request.

  8. 框架层不断地提交请求,并调用construct_default_request_settings以获得其他使用用例的默认设置缓冲区。

    The framework continues to submit requests, and call construct_default_request_settings to get default settings buffers for other use cases.

    当版本为CAMERA_DEVICE_API_VERSION_3_1或更低时,框架层可能会为那些还未注册的流调用register_stream_buffers()方法。

    <= CAMERA_DEVICE_API_VERSION_3_1: The framework may call register_stream_buffers() at this time for not-yet-registered streams.

  9. 当捕获请求开始处理(传感器开始为捕获图像进行曝光)或当再处理请求开始被处理时,HAL将调用camera3_callback_ops_t->notify(),该次调用将以SHUTTER事件作为参数,这包括帧序号和曝光开始时间戳。对于再处理请求,时间戳必须是输入图像的曝光开始时间,在process_capture_request()调用时,可以从camera3_capture_request_t.settingsandroid.sensor.timestamp获得该时间。

    When the capture of a request begins (sensor starts exposing for thecapture) or processing a reprocess request begins, the HAL calls camera3_callback_ops_t->notify() with the SHUTTER event, including the frame number and the timestamp for start of exposure. For a reprocess request, the timestamp must be the start of exposure of the input image which can be looked up with android.sensor.timestamp from camera3_capture_request_t.settings when process_capture_request() is called.

    当版本低于或等于CAMERA_DEVICE_API_VERSION_3_1时,这个通知必须在process_capture_result()第一次为处理该帧的调用前完成。

    <= CAMERA_DEVICE_API_VERSION_3_1: This notify call must be made before the first call to process_capture_result() for that frame number.

    当版本高于或等于CAMERA_DEVICE_API_VERSION_3_2时,携带SHUTTER事件的camera3_callback_ops_t->notify()应该被尽可能早的完成,因为在接收到一个有效的曝光开始时间戳(对于再处理请求则是输入图像的开始曝光时间戳)前框架层不能将(该帧的)图形缓冲区传递到应用层。

    >= CAMERA_DEVICE_API_VERSION_3_2: The camera3_callback_ops_t->notify() call with the SHUTTER event should be made as early as possible since the framework will be unable to deliver gralloc buffers to the application layer (for that frame) until it has a valid timestamp for the start of exposure (or the input image’s start of exposure for a reprocess request).

    部分元数据和图形缓冲区可能在SHUTTER时间发生前或后的任何时间返回。

    Both partial metadata results and the gralloc buffers may be sent to the framework at any time before or after the SHUTTER event.

  10. 部分流水线阻塞等待一段时间后,HAL将调用camera3_callback_ops_t->process_capture_result()将完成的捕获结果返回框架层。结果的返回顺序将和请求提交的顺序一致。多个请求可以被同时处理,这取决于相机HAL设备的流水线深度。

    After some pipeline delay, the HAL begins to return completed captures to the framework with camera3_callback_ops_t->process_capture_result(). These are returned in the same order as the requests were submitted. Multiple requests can be in flight at once, depending on the pipeline depth of the camera HAL device.

    当版本高于或等于CAMERA_DEVICE_API_VERSION_3_2时,一旦process_capture_result将缓冲区作为camera3_stream_buffer_t数组的一部分返回且release_fence指定的栅栏发出信号(对于-1栅栏而言是无操作),该缓冲区的所有权将被视为转移给框架层。之后,HAL将不会保留该缓冲区,框架层能够立即清理其占用的内存。

    >= CAMERA_DEVICE_API_VERSION_3_2: Once a buffer is returned by process_capture_result as part of the camera3_stream_buffer_t array, and the fence specified by release_fence has been signaled (this is a no-op for -1 fences), the ownership of that buffer is considered to be transferred back to the framework. After that, the HAL must no longer retain that particular buffer, and the framework may clean up the memory for it immediately.

    对于同一帧,process_capture_result可能会被多次调用,每一次都携带一个新的不相交的元数据碎片和/或设置图形缓冲区。框架层将合并这些分块的元数据到一个结构中。

    process_capture_result may be called multiple times for a single frame, each time with a new disjoint piece of metadata and/or set of gralloc buffers. The framework will accumulate these partial metadata results into one result.

    特别的,只要上述规则适用于图形缓冲区(不论输入或输出),同时为第N帧和第N+1帧调用process_capture_result是合法的。

    In particular, it is legal for a process_capture_result to be called simultaneously for both a frame N and a frame N+1 as long as the above rule holds for gralloc buffers (both input and output).

  11. 一段时间后,框架层可能停止提交新的请求,并等待现有的捕获完成(所有的缓冲区完成填充且所有的请求返回),然后再次调用configure_streams()。这将为一组新的输入/输出流重置相机硬件和流水线。某些之前的流可以被复用,如果这些流的缓冲区已经被注册到HAL中了则不需要再次注册。如果至少有一个完成注册的输出流被保留,则框架层从步骤7开始(否则从步骤5开始)。

    After some time, the framework may stop submitting new requests, wait for the existing captures to complete (all buffers filled, all results returned), and then call configure_streams() again. This resets the camera hardware and pipeline for a new set of input/output streams. Some streams may be reused from the previous configuration; if these streams’ buffers had already been registered with the HAL, they will not be registered again. The framework then continues from step 7, if at least one registered output stream remains (otherwise, step 5 is required first).

  12. 同时,框架层可能调用camera3_device_t->common->close()终止相机会话。这可能在没有其他任何来自框架层的请求活动的任何时候被调用,但该调用将会被阻塞直到所有正在处理的捕获完成(所有结果返回且所有缓冲区被填充)。在close调用返回后,不允许从HAL调用camera3_callback_ops_t函数。一旦close()的调用开始运行,框架层不能调用任何其他HAL设备函数。

    Alternatively, the framework may call camera3_device_t->common->close() to end the camera session. This may be called at any time when no other calls from the framework are active, although the call may block until all in-flight captures have completed (all results returned, all buffers filled). After the close call returns, no more calls to the camera3_callback_ops_t functions are allowed from the HAL. Once the close() call is underway, the framework may not call any other HAL device functions.

  13. 当有错误或其他异步事件发生时,HAL必须调用camera3_callback_ops_t->notify()传递恰当的错误/事件信息。在从一个设备范围的致命的错误通知返回后,HAL应该表现的像对其完成close()调用一样。同时,在调用notify()前HAL必须放弃或完成所有未解决的捕获,因此一旦调用notify()发送致命错误信息,框架层将不会从设备中接收到进一步的回调。此外,在发送错误信息的notify()方法返回后,close()应该返回-ENODEVNULL

    In case of an error or other asynchronous event, the HAL must call camera3_callback_ops_t->notify() with the appropriate error/event message. After returning from a fatal device-wide error notification, the HAL should act as if close() had been called on it. However, the HAL must either cancel or complete all outstanding captures before calling notify(), so that once notify() is called with a fatal error, the framework will not receive further callbacks from the device. Methods besides close() should return -ENODEV or NULL after the notify() method returns from a fatal error message.

这(Camera device HAL 3.6)是当前推荐使用的HAL版本。

This is the current recommended version of the camera device HAL.

其支持android.hardware.CameraAPI,但自从v3.2版本以来,android.hardware.camera2 API被设置为LIMITED或above hardware level状态。

Supports the android.hardware.Camera API, and as of v3.2, the android.hardware.camera2 API as LIMITED or above hardware level.

支持该版本的相机设备必须在camera_device_t.common.version和camera_info_t.device_version(由camera_module_t.get_camera_info获得)中返回CAMERA_DEVICE_API_VERSION_3_6。

Camera devices that support this version of the HAL must return CAMERA_DEVICE_API_VERSION_3_6 in camera_device_t.common.version and in camera_info_t.device_version (from camera_module_t.get_camera_info).

CAMERA_DEVICE_API_VERSION_3_3
或更高版本:包含3.3或更高版本的设备的相机模块至少需要支持2.2版本的相机模块接口(在camera_module_t.common.module_api_version中定义)。

CAMERA_DEVICE_API_VERSION_3_3 and above: Camera modules that may contain version 3.3 or above devices must implement at least version 2.2 of the camera module interface (as defined by camera_module_t.common.module_api_version).

CAMERA_DEVICE_API_VERSION_3_2
:包含3.2版本的设备的相机模块至少需要支持2.2版本的相机模块接口(如camera_module_t.common.module_api_version所定义的)。

CAMERA_DEVICE_API_VERSION_3_2: Camera modules that may contain version 3.2 devices must implement at least version 2.2 of the camera module interface (as defined by camera_module_t.common.module_api_version).

CAMERA_DEVICE_API_VERSION_3_1
或更低版本:包含3.1或更低版本的设备的相机模块至少需要支持2.0版本的相机模块接口(如camera_module_t.common.module_api_version所定义的)。

<= CAMERA_DEVICE_API_VERSION_3_1: Camera modules that may contain version 3.1 (or 3.0) devices must implement at least version 2.0 of the camera module interface (as defined by camera_module_t.common.module_api_version).

更多与版本相关的细节可参考camera_common.h。

See camera_common.h for more versioning details.

文档目录

Documentation index:

S1:版本历史(未开始)

S1. Version history

S2:启动和操作顺序

S2. Startup and operation sequencing

S3:操作模式(未开始)

S3. Operational modes

S4:3A模式和状态机(未开始)

S4. 3A modes and state machines

S5. 裁剪(未开始)

S5. Cropping

S6. 错误管理(未开始)

S6. Error management

S7. 关键性能指标(KPI)术语表(未开始)

S7. Key Performance Indicator (KPI) glossary

S8. 使用示例(未开始)

S8. Sample Use Cases

S9. 关于控件和元数据的说明(未开始)

S9. Notes on Controls and Metadata

S10. 再处理流程和控制(未开始)

S10. Reprocessing flow and controls

GStreamer引用计数查看方法

GStreamer提供了GST_OBJECT_REFCOUNT_VALUE宏用于查询引用计数,其要求输入一个GstElement*类型的变量,并返回gint类型的该变量引用计数值。

注意,当输入变量未被分配或已释放(输入变量的引用计数降为0)时,该宏的行为未定义。

Pipeline释放前未设置状态为NULL时可能导致的内存泄露

当Pipeline释放前未设置状态为NULL时,其具有许多外部的引用计数,此时即使将其unref,也不会使得管道被释放,这将可能导致内存泄露。

使用如下代码进行测试:

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
30
31
32
33
34
35
#include <gst/gst.h>

gboolean stop(gpointer data);

int main(int argc, char* argv[]) {
gst_init(&argc, &argv);

GstElement* pipeline = gst_parse_launch("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
GMainLoop* main_loop = g_main_loop_new(NULL, FALSE);

g_timeout_add(5000, stop, main_loop);

g_print("pipeline ref on a: %d\n", GST_OBJECT_REFCOUNT_VALUE(pipeline));

gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_print("pipeline ref on b: %d\n", GST_OBJECT_REFCOUNT_VALUE(pipeline));
g_main_loop_run(main_loop);
g_print("pipeline ref on c: %d\n", GST_OBJECT_REFCOUNT_VALUE(pipeline));

//gst_element_set_state(pipeline, GST_STATE_NULL);
g_print("pipeline ref on d: %d\n", GST_OBJECT_REFCOUNT_VALUE(pipeline));

gst_object_unref(pipeline);
g_print("pipeline ref on e: %d\n", GST_OBJECT_REFCOUNT_VALUE(pipeline));
g_main_loop_unref(main_loop);
return 0;
}

gboolean stop(gpointer data) {
GMainLoop* loop = (GMainLoop*)data;
g_print("STOP\n");
g_main_loop_quit(loop);

return FALSE;
}

运行后,控制台打印结果为:

1
2
3
4
5
6
pipeline ref on a: 1
pipeline ref on b: 3
STOP
pipeline ref on c: 7
pipeline ref on d: 7
pipeline ref on e: 6

当正确设置状态为NULL时可避免该问题:

1
2
3
4
5
pipeline ref on a: 1
pipeline ref on b: 3
STOP
pipeline ref on c: 7
pipeline ref on d: 1

运行后,控制台打印结果为:

1
2
3
4
5
pipeline ref on a: 1
pipeline ref on b: 3
STOP
pipeline ref on c: 7
pipeline ref on d: 1

项目结构如下:

1
2
3
4
5
6
7
8
9
10
project
├─include
│ └─hello.hpp
├─src
│ ├─lib
│ │ └─hello.cpp
│ └─main.cpp
├─test
│ └─my_test.cpp
└─CMakeLists.txt

使用如下CMake进行编译:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 设置CMake最低版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(demo)

# 设置编译模式为Debug
set(CMAKE_BUILD_TYPE "Debug")

# 设置需要的CPP标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)

# 打开测试
enable_testing()

# 添加头文件路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

# 设置库源文件文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src/lib LIB_SRCS)

# 添加静态库目标
add_library(hello STATIC ${LIB_SRCS})

# 设置可执行文件源文件
file(GLOB MAIN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

# 添加可执行文件目标
add_executable(${PROJECT_NAME} ${MAIN_SRCS})

# 链接静态库
target_link_libraries(${PROJECT_NAME} hello)

# 设置测试文件源文件
file(GLOB TEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp)

# 为每一个测试文件
foreach(SRC IN LISTS TEST_SRCS)
# 生成测试文件二进制文件名
get_filename_component(FILE_NAME ${SRC} NAME_WE)

# 生成测试文件二进制文件
add_executable(${FILE_NAME} ${SRC})

# 链接静态库
target_link_libraries(${FILE_NAME} hello)

# 添加测试命令
add_test(NAME ${FILE_NAME} COMMAND ${FILE_NAME})
endforeach()

锁机制原理

锁机制源于信号量,其实现与操作系统息息相关。一般而言,线程存在下图所示的5种状态,其中当线程处于阻塞状态时,不会调度到处理机中运行。

线程状态

信号量机制则为在需互斥线程间共享一个数据,同时提供PV两种操作,两种操作的行为如下:

  1. P操作:将数据减一,当数据小于零时,将当前线程阻塞。

  2. V操作:将数据加一,若此时存在线程阻塞在该信号量时,则唤醒其中一个。

当信号量中使用布尔变量作为共享数据时,则仅允许一个线程进入临界区,实现并发互斥。通常将该信号量称为锁。

CPP中的锁

分类

在CPP中,根据不同的标准,大致可以分为如下部分:

其中,各标准的含义如下:

  1. 可重入:在一个线程获得目标锁后,再次请求同一把锁时不会阻塞。

  2. 限时性:在线程阻塞等待目标锁时,可以设置超时时间,超时时即使未获得目标锁也会解除阻塞。(不是锁的通常分类,但CPP实现中将其区分出来。)

  3. 分读写:上锁操作分为读锁和写锁,其中,写锁和互斥锁相同,一旦加上便无法再加上其他写锁和读锁;读锁则可以加上多重,即加上后可以再加上其他读锁,但无法再加上写锁。

根据上述分类标准,CPP中的锁的属性如下:

锁类型 可重入 可限时 分读写
mutex n n n
timed_mutex n y n
recursive_mutex y n n
recursive_timed_mutex y y n
shared_mutex n n y
shared_timed_mutex n y y

公共接口

所有锁都提供了一些相似的接口,例如,所有的锁只能够调用默认构造函数创建,不能够进行拷贝和移动。其他相似接口如下:

接口 含义
void lock(); 获得目标锁,若无法获得则阻塞当前线程。
如果使用的锁可重入,则同一线程可多次调用,否则会在第二次调用时发生死锁。
bool try_lock() noexcept; 尝试获得锁,若无法立即获得也会立即返回false,否则获得该锁并返回true。
void unlock(); 释放目标锁。

上述接口所有的锁都具有,因此在锁的RAII机制中具有重要作用。

限时接口

所有可限时的锁都提供了一些相似的接口,这些相似接口如下:

接口 含义
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout_duration);
尝试获得目标锁,若未能在timeout_duration的时间内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_duration小于等于timeout_duration.zero()时等价于try_lock。
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& timeout_time)
尝试获得目标锁,若未能在时间timeout_time前内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_time早于当前时间时等价于try_lock。

读写接口

所有分读写的锁使用lock接口作为写锁接口,都提供了一些相似的读锁接口,这些相似接口如下:

接口 含义
void lock_shared(); 获得目标读锁,若无法获得则阻塞当前线程。
bool try_lock_shared() noexcept; 尝试获得目标读锁,若无法立即获得也会立即返回false,否则获得该锁并返回true。
void unlock_shared(); 释放目标锁。

读写限时接口

所有分读写且可限时同时也提供了一些相似的可限时读锁接口,这些相似接口如下:

接口 含义
template <class Rep, class Period>
bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& timeout_duration);
尝试获得目标锁,若未能在timeout_duration的时间内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_duration小于等于timeout_duration.zero()时等价于try_lock_shared。
template <class Clock, class Duration>
bool try_lock_shared_until(const std::chrono::time_point<Clock, Duration>& timeout_time)
尝试获得目标锁,若未能在时间timeout_time前内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_time早于当前时间时等价于try_lock_shared。

CPP在锁上的RAII机制

CPP提供了四个类用于实现基于RAII的锁机制,这些类均能够在构造函数中锁上传入的锁,在析构函数中释放对应的锁,有效的解决了因代码改动新加分支等原因导致的锁未释放问题。

同时,还允许通过传入参数进行锁定策略的修改,相应的锁定策略如下:

策略参数 策略
<默认> 调用传入参数的上锁方法,会发生阻塞。
std::defer_lock 假设当前未获得锁,之后会手动申请。
std::try_to_lock 尝试获取锁,但有可能失败,这不会阻塞。
std::adopt_lock 假设当前已经获取了锁。
std::chrono::duration 调用传入参数的lock_for方法,会发生阻塞。
std::chrono::time_point 调用传入参数的lock_until方法,会发生阻塞。

实现RAII机制的类的类型和特点如下:

类型 特点 支持策略
lock_guard 不可移动,不可拷贝 adopt_lock
scoped_lock 不可移动,不可拷贝。
能够一次申请多把锁。
adopt_lock
unique_lock 可移动,不可拷贝 defer_lock、try_to_lock、adopt_lock、duration、time_point
shared_lock 可移动,不可拷贝。
申请的是读锁。
defer_lock、try_to_lock、adopt_lock、duration、time_point

同时,unique_lockshared_lock还提供了如下接口:

接口 含义
void lock(); 获得目标锁,若无法获得则阻塞当前线程。
如果使用的锁可重入,则同一线程可多次调用,否则会在第二次调用时发生死锁。
bool try_lock() noexcept; 尝试获得锁,若无法立即获得也会立即返回false,否则获得该锁并返回true。
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout_duration);
尝试获得目标锁,若未能在timeout_duration的时间内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_duration小于等于timeout_duration.zero()时等价于try_lock。
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& timeout_time)
尝试获得目标锁,若未能在时间timeout_time前内获得该锁则放弃申请,返回false;否则获得该锁,返回true。
timeout_time早于当前时间时等价于try_lock。
void unlock(); 释放目标锁。
mutex_type* release() noexcept; 未释放目标锁的放弃对目标锁的控制权。
bool owns_lock() const noexcept; 返回其是否持有已锁定的锁。
explicit operator bool() const noexcept; 隐式类型转换函数,相当于调用owns_lock。

CPP提供的通用锁定方法

CPP提供以下能够获取锁的方法:

接口 含义
template <class Lockable1, class Lockable2, class… LockableN>
void lock(Lockable1& lock1, Lockable2& lock2, LockableN&… lockn);
使用死锁避免算法尝试获得多把锁。
template <class Lockable1, class Lockable2, class… LockableN>
int try_lock(Lockable1& lock1, Lockable2& lock2, LockableN&… lockn);
从头开始锁定传入的锁,如果存在失败的,则立即返回失败锁在传入参数的以0为底的下标;若都获取成功,返回-1。

CPP条件变量

条件变量将会是CPP线程同步的重要机制,其原理和信号量类似,是通过线程的阻塞和唤醒实现的。

CPP中提供了condition_variablecondition_variable_any作为条件变量,两者完全一致,唯一的区别是condition_variablewait时只能使用std::unique<std::mutex>作为参数而condition_variable_anywait时能够使用任意锁作为参数。

两者提供了相似的接口,具体如下:

接口 含义
void notify_one() noexcept; 唤醒正在等待该条件变量的一个线程。
void notify_all() noexcept; 唤醒正在等待该条件变量的所有线程,即使被唤醒线程因为锁竞争等原因再次阻塞也不会再次等待条件变量信号。
void wait(Lock& lock); 等待条件变量唤醒信号,要求lock已经由当前线程持有。
void wait(Lock& lock, Predicate pred); 相当于while(!pred()) { wait(lock); }。
std::cv_status wait_until(Lock& lock, const std::chrono::time_point<Clock, Duration>& abs_time); 等待条件变量唤醒信号或到达指定时间,要求lock已经由当前线程持有。
bool wait_until(Lock& lock, const std::chrono::time_point<Clock, Duration>& abs_time, Predicate pred); 相当于while(!pred) { if(wait_until(lock, abs_time) == std::cv_status::timeout) { return pred(); } } return true;。
std::cv_status wait_for(Lock& lock, const std::chrono::duration<Rep, Period>& rel_time); 相当于wait_until(lock, std::chrono::steady_clock::now() + rel_time)。
bool wait_for(Lock& lock, const std::chrono::duration<Rep, Period>& rel_time, Predicate pred); 相当于wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));。

其中,std::cv_status包含以下类型:

  1. std::cv_status::no_timeout:未超时

  2. std::cv_status::timeout:超时

基本介绍

gdb是Linux下常用的命令行调试器,在Debian下使用如下命令安装:

1
sudo apt install gdb -y

可以使用如下三种方式运行gdb程序,对目标二进制文件进行调试:

  1. gdb <file_name>:运行目标文件,从头开始调试。

  2. gdb <file_name> <core_name>:根据core文件对目标文件进行调试。

  3. gdb <file_name> <pid>:远程调试,调试目标文件的对应正在运行的进程。

在进入调试界面后,可以使用一些调试命令以更好地对程序进行调试,其主要分为以下几类:

  1. 流程控制:按照使用者想法运行程序。

  2. 断点管理:管理断点,使得程序在运行时满足某种条件时能够暂停。

  3. 信息输出:输出使用者希望输出的信息。

  4. 环境设置:动态改变程序内部或外部的执行环境。

流程控制

指令 缩写 作用
run [<arg>] r 运行未运行的程序
cotinue [<ignore_num>] c 在程序被中断后继续运行
step [<inst_num>] s 执行inst_num条指令,遇到函数时进入函数(如果该函数有调试信息)
next [<inst_num>] n 执行inst_num条指令,遇到函数视为一条指令,不会进入函数内部
finish fin 执行直到当前函数返回,并打印返回值
until [<target>] u 执行完当前循环,若目标位置被指定则执行到目标位置,除非函数返回

断点管理

断点

断点用于观察控制流的运行位置,当控制流运行到断点时停止程序。

指令 缩写 作用
break [<pos> [if <cond>]] b 设置断点在pos,如设置了cond,则当条件未达成时不触发断点

其中pos的设置如下:

  1. <file_name>:<line_num>:在进入指定文件对应行号的指令前设置断点。

  2. <file_name>:<func_name>:在进入指定文件对应函数的指令前设置断点。

  3. *<inst_addr>:在进入指定地址对应的指令前设置断点。

观察点

观察点用于观察目标表达式的值是否发生变化。目标表达式的值发生变化时停止程序。

指令 缩写 作用
watch [<expr> [if <cond>]] w 若目标表达式的值发生变化,则停止程序
rwatch [<expr> [if <cond>]] rw 若目标表达式被读,则停止程序
awatch [<expr> [if <cond>]] aw 若目标表达式被读或写,则停止程序

捕捉点

捕获点用于捕获当前程序发生的行为,当指定行为发生时停止程序。

指令 缩写 作用
catch <event> cat 发生event对应事件时停止程序
tcatch <event> tc 发生event对应事件时停止程序,只会触发一次

其中event如下:

event 事件
throw C++抛出异常时
catch C++捕获异常时
exec 调用exec时,只在HP-UX下有用(修改进程运行程序)
fork 调用fork时,只在HP-UX下有用(创建进程)
vfork 调用vfork时,只在HP-UX下有用(创建进程)
load 调用load时,只在HP-UX下有用(加载动态库)
unload 调用unload时,只在HP-UX下有用(卸载动态库)

停止点控制

指令 缩写 作用
clear [<pos_range>] cl 删除指定位置范围的停止点
delete [<id_list>] del 根据停止点ID删除对应停止点
disable [<id_list>] dis 根据停止点ID停用对应停止点
enable [<id_list>] en 根据停止点ID启用对应停止点
condition <id> <cond> cond 给对应停止点添加触发条件
ignore <id> <ign_num> ig 忽略对应停止点指定次数

信息输出

指令 缩写 作用
print <expr> p 打印指定变量或表达式的值
display <expr> disp 追踪目标表达式的值,每次停止时打印追踪的表达式值
info <sign> i 打印gdb内部信息
x <addr> 打印内存信息

环境设置

指令 缩写 作用
set <context> 设置环境变量或gdb状态等

安装Chocolatey

使用Chocolatey进行包管理,其安装方法详见chocolatey安装

  1. 使用管理员权限运行powershell

  2. 执行如下命令。

    1
    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
  3. 输入choco -?验证是否安装成功。

安装依赖包

  1. 使用管理员权限运行powershell

  2. 执行如下命令。

    1
    choco install --yes mingw make cmake git
  3. 执行如下命令验证是否安装成功:

    1
    2
    3
    4
    5
    gcc --version
    g++ --version
    make --version
    cmake --version
    git --version

Future机制

CPP Future机制用于获取异步任务的异常和返回结果,尽管也可以通过传入的指针或引用返回结果,但其需要引入外部变量,较为麻烦,且难以捕获异常。

Future机制由promise和future配合实现异步返回,promise用于在异步任务中设置返回结果,future用于获取在原线程中获取异步任务返回结果,两者间通过共享状态(shared state)进行通信。三者关系如下图:

CPP中的promise和future

CPP提供了std::promise充当Future机制中的promise,std::futurestd::shared_future充当Future机制中的future,两者共同作用解决完成异步数据传输。

promise

CPP提供了std::promise充当Future机制中的promise,std::promise只能进行默认构造和移动,无法进行拷贝。

std::promise提供如下接口进行数据传输:

方法 含义
std::future<R> get_future(); 返回一个与promise关联相同共享状态的std::future对象
void set_value(const R& value);
void set_value(R&& value);
立即设置promise的共享状态的值
void set_exception( std::exception_ptr p ); 立即设置promise的共享状态捕获的异常
void set_value_at_thread_exit(const R &value);
void set_value_at_thread_exit(R &&value);
在线程结束后设置promise的共享状态的值
void set_exception_at_thread_exit(std::exception_ptr p); 在线程结束后设置promise的共享状态捕获的异常

在使用std::promise时有以下注意事项:

  1. std::promise是一次性的,一旦设置了相应的值,再次调用设值函数将会抛出异常。

  2. std::promise的任何方法都不会断开与共享状态的连接,因此可以多次调用get_future

  3. set_*set_*_at_thread_exit之间的区别在于,set_*将会使得与该对象内部的共享状态相关联的future将能够立即获得值;set_*_at_thread_exit将会使得与该对象内部的共享状态相关联的future将在异步线程退出后才获得值。

future

CPP提供了std::futurestd::shared_future充当Future机制中的future,两者提供了一些相同的接口用于实现异步线程数据传输:

方法 含义
T get();
T& get();
获取共享状态的值或捕获的异常。
若此时共享状态未被设置值或捕获异常则进行阻塞等待
bool valid() const noexcept; 当future绑定共享状态时返回true,否则返回false
void wait() const; 阻塞等待,直到共享状态被设置值或捕获异常
template <class Rep, class Period>
std::future_status wait_for(const std::chrono::duration<Rep,Period>& timeout_duration) const;
阻塞等待,直到共享状态被设置值或捕获异常,或者线程已被阻塞了timeout_duration时间
template <class Clock, class Duration>
std::future_status wait_until(const std::chrono::time_point<Clock,Duration>& timeout_time) const;
阻塞等待,直到共享状态被设置值或捕获异常,或者线程被阻塞直到timeout_time

尽管std::futurestd::shared_future提供了相似的异步线程数据传输接口,但二者具有以下区别:

std::future std::shared_future
只能进行默认构造或移动,不能进行拷贝 能进行默认构造、拷贝和移动
无法进行安全的线程共享 同一个实例无法安全的进行线程共享
拷贝生成的示例能够在多线程环境下安全的对共享状态的值进行赋值
调用get函数后将与相应的共享状态切断联系,因此只能调用一次 调用get函数后不与相应的共享状态切断联系,因此能够调用多次
能够通过std::shared_funtre<T> share() noexcept;转化为std::shared_future
调用该方法后将与相应的共享状态切断联系
能够接收std::funtre<T> &&进行构造
调用该方法后输入的std::funtre<T> &&将与相应的共享状态切断联系

对于wait_forwait_until,可以通过返回值判断其返回时的状态,其返回值可能为:

  1. std::future_status::ready:共享状态就绪。

  2. std::future_status::timeout:等待超时。

  3. std::future_status::deferred:共享状态关联的是一个deferred的函数,只有在调用get时才能赋值(详见std::async)。

代码示例

下面给出一个简单的代码示例:

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
30
31
32
33
34
35
36
37
#include <iostream>
#include <future>

void test_value() {
std::promise<int> pro;
std::future<int> fut = pro.get_future();
std::cout << fut.valid() << std::endl;
pro.set_value(1024);
std::cout << fut.valid() << std::endl;
std::cout << fut.get() << std::endl;
std::cout << fut.valid() << std::endl;
}

void test_execption() {
std::promise<int> pro;
std::future<int> fut = pro.get_future();
try {
throw std::runtime_error("Example");
} catch (...) {
try {
std::cout << "Catch" << std::endl;
pro.set_exception(std::current_exception());
} catch (...) {
std::cout << "Error" << std::endl;
}
} try {
std::cout << fut.get() << std::endl;
} catch (const std::exception &e) {
std::cout << "Throw: " << e.what() << '\n';
}
}

int main(int argc, char *argv[]) {
test_value();
test_execption();
return 0;
}

运行后打印:

1
2
3
4
5
6
1
1
1024
0
Catch
Throw: Example

CPP异步任务

为了便于创建和使用异步任务而无需手动管理promisefuture,CPP提供了std::packaged_taskstd::async用于构建异步任务。

packaged_task

CPP提供了std::packaged_task类用于方便的将已有函数打包为异步可调用对象,其提供了默认构造函数、基于仿函数的构造函数、移动构造函数和移动赋值函数来设置相应的值,并提供以下方法:

方法 含义
bool valid() const noexcept; 返回packaged_task内部是否有有效的仿函数
std::future get_future(); 返回一个与packaged_task关联的future对象
可以多次调用返回多个关联相同共享状态的future
void operator()(ArgTypes… args); 使用args作为参数调用packaged_task内的仿函数,并立即将调用结果赋值到共享状态中
void make_ready_at_thread_exit(ArgTypes… args); 使用args作为参数调用packaged_task内的仿函数,调用结果将在异步线程结束后赋值到共享状态中
void reset(); 重置内部状态,其所关联的future全部失效,但具有的仿函数不变

下面是一个简单的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>
#include <future>

std::string func(int s) {
return std::to_string(s | INT32_MIN);
}

int main(int argc, char *argv[]) {
std::packaged_task<std::string(int)> ptask(func);
std::future<std::string> f = ptask.get_future();
std::thread th(std::move(ptask), INT32_MAX);
std::cout << f.get() << std::endl;
th.join();
return 0;
}

运行后将获得如下结果:

1
-1

async

CPP提供了std::async函数方便的创建并运行异步任务,其函数签名如下:

1
2
3
4
5
template <class F, class ... Args>
std::future<...> async(F&& f, Args&& ... args);

template <class F, class ... Args>
std::future<...> async(std::launch policy, F&& f, Args&& ... args);

其中,不带policy参数的版本相当于向带参数版本中传入policy参数std::launch::async | std::launch::deferred

该函数能够创建一个异步任务,其有两种执行方式:

  1. 异步调用,在另一个线程(新线程或线程池中的线程)中运行异步任务,并设置共享状态传递执行结果。

  2. 延迟调用,当future调用waitget时在被阻塞线程运行异步任务并通过设置共享状态传递执行结果,调用wait_forwait_until时返回std::future_status::deferred

可以通过policy参数设置开启的异步任务的执行方式:

  1. 同时设置std::launch::asyncstd::launch::deferred:由标准库和当前系统状态进行决定,一般来说,当系统线程数量达到上限或系统线程超发(就绪线程数大于CPU并发线程数)时会采用延迟调用,否则会采用异步调用。

  2. 仅设置std::launch::async:总是进行异步调用。

  3. 仅设置std::launch::deferred:总是进行延迟调用。

  4. 未设置std::launch::asyncstd::launch::deferred:行为未定义。

设置目录默认展开

  1. 修改themes/next/_config.yml,搜索custom_file_path,去除style前的#注释符。

  2. 创建source/_data/styles.styl文件,添加下述内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //文章目录默认展开
    .post-toc .nav .nav-child {
    display: block;
    }

    //文章目录字体大小调整
    .post-toc ol {
    font-size : 13px;
    }
  3. 修改themes/next/_config.yml,搜索toc,将wrap设为true

设置中文目录跳转

next中,有时中文目录难以跳转,通过浏览器检查,可以发现如下问题:

根据Github issue的回应,对themes/next/source/js/utils.js修改如下,需要删除两行、添加两行:

添加的两行分别为:

1
2
3
var target = document.getElementById(decodeURI(link.getAttribute('href')).replace('#', ''));

return target;

Windows 系统自带工具 certutil

在Cmd终端或PowerShell中使用certutil,命令如下:

1
certutil -hashfile <file_name> MD5

输出结果为<file_name>的MD5值。

使用 Git Bash 命令窗口中的 md5sum.exe

如果安装了Git,鼠标右键点击时可以在菜单中选择进入Git Bash,其包含md5sum.exe计算文件MD5值:

1
md5sum.exe <file_name>

输出结果为<file_name>的MD5值。