From bf6fa9a139e262d35ea26849367d538d84478587 Mon Sep 17 00:00:00 2001 From: lijincheng Date: Wed, 31 Jan 2024 18:07:39 +0800 Subject: [PATCH] Update document 1.Supply typical cases 2.Update interface description Issue:https://gitee.com/openharmony/commonlibrary_c_utils/issues/I909QG Signed-off-by: lijincheng --- docs/en/c_utils_timer.md | 39 ++++++++++++- docs/zh-cn/c-utils-guide-parcel.md | 88 ++++++++++++++++++++++++++++- docs/zh-cn/c-utils-guide-safeMap.md | 36 +++++++++++- docs/zh-cn/c-utils-guide-thread.md | 15 +++++ docs/zh-cn/c_utils_timer.md | 39 ++++++++++++- 5 files changed, 212 insertions(+), 5 deletions(-) diff --git a/docs/en/c_utils_timer.md b/docs/en/c_utils_timer.md index b83ed4b..34b202b 100644 --- a/docs/en/c_utils_timer.md +++ b/docs/en/c_utils_timer.md @@ -40,4 +40,41 @@ run -t UT -tp utils -ts UtilsTimerTest 1. Parameter in Shutdown() determines whether the thread in Timer would be detach or join. (True(default) --> join; False --> detach). Detach operation would cause possible multithreading problems, thus is not recommended. If a detach operation is required, availability of related objects used in `thread_` should be guaranteed. -1. If the system sleeps during the scheduled task, the Timer cannot wake up automatically during the sleep phase, and will not perform counting operations, which will cause abnormal timing results. \ No newline at end of file +1. If the system sleeps during the scheduled task, the Timer cannot wake up automatically during the sleep phase, and will not perform counting operations, which will cause abnormal timing results. + +## Typical Cases +1. The unregister of Timer has a critical situation. The deregistration of the Timer may trigger the deletion of the corresponding event at the time when the event is responded. In this case, an additional callback response may be triggered. + +```cpp +// pseudocode +Timer timer("timer_test"); +CallBack func; +timer.Setup(); +uint32_t timerId = timer.Register(func, 1000); // The scheduled response is called every one minute. Assume that the scheduled response takes effect at 00:00. +...... +/* +Event deletion is triggered at 1:00, which is in the critical state. If the polling thread obtains the response event earlier than the deletion action, it will find that the response event is triggered once more after deregistration. If the polling thread responds later than the deletion, the polling thread is not aware of the event response. After the deregistration, the corresponding event is not triggered again. +*/ +timer.Unregister(timerId); + +``` + +2. To save resources and improve performance, timerFd is reused for events with the same interval. As a result, the response time of some events may be different from the expected time. If developers have strong requirements on the response time, we advise to set interval to a slightly different time (for example, 1 ms). + +```cpp +// pseudocode +Timer timer("timer_test"); +CallBack func1; +CallBack func2; +timer.Setup(); + +// Assume that the start time is 00:00, the subsequent responses of func1 are 01:00, 02:00, 03:00, 04:00... +uint32_t timerId_1 = timer.Register(func1, 1000); // One-minute cyclic response callback. + +// Assume that the start time of func2 is 0:30 and the expected follow-up responses are 1:30, 2:30, 3:30, 4:30... +// However, timer fd is multiplexed and shared with func1. The subsequent time response is the same as that of func1, which is 1:00, 2:00, 3:00, 4:00... +uint32_t timerId_2 = timer.Register(func2, 1000); // The timerfd is multiplexed with the timerfd of func1. + +// Assume that the start time of func2 is 0:30, the subsequent responses of func2 are about (with 1ms deviation) 1:30, 2:30, 3:30, 4:30... +uint32_t timerId_3 = timer.Register(func2, 1001); // Func2 has its own timerfd. +``` \ No newline at end of file diff --git a/docs/zh-cn/c-utils-guide-parcel.md b/docs/zh-cn/c-utils-guide-parcel.md index c8827e1..bd8a6e3 100644 --- a/docs/zh-cn/c-utils-guide-parcel.md +++ b/docs/zh-cn/c-utils-guide-parcel.md @@ -143,7 +143,7 @@ class OHOS::Parcel; | bool | **WriteUint8**(uint8_t value) | | bool | **WriteUint8Unaligned**(uint8_t value) | | bool | **WriteUInt8Vector**(const std::vector< uint8_t >& val) | -| bool | **WriteUnpadBuffer**(const void* data, size_t size) | +| bool | **WriteUnpadBuffer**(const void* data, size_t size)
基于数据区指针及数据长度写入一段数据,功能与WriteBuffer完全相同,`注:`该接口内部会自动计算并写入对齐长度| | template
bool | **WriteVector**(const std::vector< T1 >& val, bool(Parcel::*)(T2) Write)
向当前parcel写入一个`std::vector`对象。 | #### Protected Functions @@ -270,7 +270,91 @@ uint16_t readuint16 = parcel.ReadUint16(); uint32_t readuint32 = parcel.ReadUint32(); ``` -2. 测试用例编译运行方法 +2. 常见接口限制及使用误区 + +- ReadBuffer/ReadUnpadBuffer/WriteBuffer/WriteUnpadBuffer + + 不推荐ReadBuffer与WriteBuffer/WriteUnpadBuffer对应配合使用,可能因为对齐问题导致ReadBuffer后的Read操作从错误的偏移位置进行读取,进而导致读取异常; + ReadUnpadBuffer与WriteBuffer/WriteUnpadBuffer配合使用为正确的使用方式。 + +```cpp +// ReadBuffer: 读取buffer,且内部数据区按参数设置长度偏移,不考虑数据对齐 +// ReadUnpadBuffer: 读取buffer,内部数据区基于读取长度自动计算对齐并偏移,将数据对齐考虑在内 +// WriteBuffer: 写入数据,内部数据区会基于写入长度计算对齐长度并自动偏移 +// WriteUnpadBuffer: 与WriteBuffer完全相同 + +struct Padded { + char title; + int32_t handle; + uint64_t cookie; +}; + +struct Unpadded { + char tip; +}; + +Parcel parcel(nullptr); +const struct Padded pad = { 'p', 0x34567890, -0x2345678998765432 }; +const struct Unpadded unpad = { 'u' }; +// CASE 1:写入对齐数据 +// 后续代码为单case下不同情况的使用代码,并非真实连续调用 +parcel.WriteBuffer(static_cast(&pad), sizeof(struct Padded)); +parcel.WriteInt32(1); + +// 错误使用但结果正常: +parcel.ReadBuffer(sizeof(struct Padded)); // 可以正常读取buffer内容 +parcel.ReadInt32(); // 后续读取内容正常 + +// 正确使用: +parcel.ReadUnpadBuffer(sizeof(struct Padded)); // 可以正常读取buffer内容 +parcel.ReadInt32(); // 后续读取内容正常 + +// CASE 2:写入非对齐数据 +// 后续代码为单case下不同情况的使用代码,并非真实连续调用 +parcel.WriteBuffer(static_cast(&unpad), sizeof(struct Unpadded)); +parcel.WriteInt32(1); + +// 错误使用,结果异常: +parcel.ReadBuffer(sizeof(struct Unpadded)); // 可以正常读取buffer内容 +parcel.ReadInt32(); // 后续读取内容异常 + +// 正确使用: +parcel.ReadUnpadBuffer(sizeof(struct Unpadded)); // 可以正常读取buffer内容 +parcel.ReadInt32(); // 后续读取内容正常 +``` + +- 基础类型的Read接口,如ReadInt32,ReadFloat等读取失败 + + 当前在基础Read接口内加入了安全校验机制,当发现基础类型的读操作在读取Object对象数据内容时,该行为会被拦截 + +```cpp +// 伪代码: + +Parcel parcel(nullptr); +Parcelable object; +parcel.WriteRemoteObject(object); +parcel.ReadInt32(); // False + +``` + +- 使用WriteBuffer写入字符串,忽略结束符导致读取字符串长度异常 + + WriteBuffer接口并非专门处理字符串写入的接口,因此错误传递写入长度,可能会导致字符串的结束符丢失 + +```cpp +// 伪代码: + +string str = "abcdefg"; +Parcel parcel(nullptr); +char *strPtr = str.c_str(); +auto len = str.length(); +parcel.WriteBuffer(strPtr, len); + +parcel.ReadBuffer(len); // 读取字符串长度异常 + +``` + +3. 测试用例编译运行方法 - 测试用例代码参见 base/test/unittest/common/utils_parcel_test.cpp diff --git a/docs/zh-cn/c-utils-guide-safeMap.md b/docs/zh-cn/c-utils-guide-safeMap.md index 40d0e58..a382efa 100644 --- a/docs/zh-cn/c-utils-guide-safeMap.md +++ b/docs/zh-cn/c-utils-guide-safeMap.md @@ -24,7 +24,8 @@ | bool | **IsEmpty**()
判断map是否为空。 | | void | **Iterate**(const SafeMapCallBack& callback)
遍历map中的元素。 | | SafeMap& | **operator=**(const SafeMap& rhs) | -| V& | **operator[]**(const K& key) | +| V | **ReadVal**(const K& key)
线程安全地读map内元素| +| void | **ChangeValueByLambda**(const K& key, LambdaCallback callback)
线程安全地操作safemap内元素,操作行为需要自定义| | int | **Size**()
获取map的size大小。 | ## 使用示例 @@ -100,4 +101,37 @@ int main() ```bash run -t UT -tp utils -ts UtilsSafeMapTest +``` + +3. 接口使用变更 + +- operator[]接口被废弃:该接口返回类型为引用,可以使用于读写场景,但在写场景情况下,该接口无法进行线程安全防护,因写行为本身基于引用由调用方触发,而非接口内部行为,接口内部持锁无法控制,因此失去线程安全的意义。 + +```cpp +SafeMap sm; +// Thread 1: +sm[1] = "abc"; + +// Thread 2: +sm[2] = "def"; +``` + +- 提供专用的线程安全读接口:ReadVal,该接口只具有safemap的读取能力,但保证线程安全 + +```cpp +SafeMap sm; +// Thread 1: +sm.Insert("A", 1); +int val = sm.ReadVal("A"); +``` + +- 针对之前利用operator[]返回引用进行的特殊元素操作行为,提供了接口ChangeValueByLambda进行替换,调用者自定义元素操作函数,可保证在线程安全的情况下操作safemap元素。 + +```cpp +SafeMap> sm; +int val = 1; +auto fn = [&](std::set &value) -> void { // 自定义callback + value.emplace(val); +} +sm.ChangeValueByLambda("A", fn); ``` \ No newline at end of file diff --git a/docs/zh-cn/c-utils-guide-thread.md b/docs/zh-cn/c-utils-guide-thread.md index 24e2eb5..6c98234 100644 --- a/docs/zh-cn/c-utils-guide-thread.md +++ b/docs/zh-cn/c-utils-guide-thread.md @@ -42,3 +42,18 @@ run -t UT -tp utils -ts UtilsThreadTest ``` ## 常见问题 + +- 主线程对象生命周期终止前,一定要调用NotifyExitSync或NotifyExitAsync终止子线程运行,否则子线程在主线程对象消亡后继续工作,而由于Run函数由主线程对象真正实现,此时主线程对象消亡,Run函数会调用虚基类的纯虚函数而报错。 + +```cpp +class RealThread : public Thread { // 使用方继承虚基类并实现Run函数 + bool Run() override; +}; + +{ + std::unique_ptr test = std::make_unique(); + ThreadStatus status = test->Start("test_thread_01", THREAD_PROI_LOW, 1024); // 创建并启动子线程对象 + test->NotifyExitSync(); // 在test对象生命周期结束前,一定要终止子线程的继续运行 +} + +``` diff --git a/docs/zh-cn/c_utils_timer.md b/docs/zh-cn/c_utils_timer.md index feaebea..9648152 100644 --- a/docs/zh-cn/c_utils_timer.md +++ b/docs/zh-cn/c_utils_timer.md @@ -41,4 +41,41 @@ run -t UT -tp utils -ts UtilsTimerTest 1. Shutdown接口的参数决定了Timer中的线程的阻塞与否,默认阻塞(true),若为false则非阻塞。非阻塞选项 可能会导致线程问题,因此不推荐。如果一定要使用非阻塞选项,请自行保证线程中对象的生命周期。 -1. 如果定时任务中发生系统休眠,在休眠阶段Timer无法自唤醒,不会执行计数操作,因此会导致计时结果异常。 \ No newline at end of file +1. 如果定时任务中发生系统休眠,在休眠阶段Timer无法自唤醒,不会执行计数操作,因此会导致计时结果异常。 + +## 典型案例 +1. Timer的unregister存在临界情况,刚好在事件响应的时间点触发对应事件的删除,可能会导致一次额外的回调响应 + +```cpp +// 伪代码 +Timer timer("timer_test"); +CallBack func; +timer.Setup(); +uint32_t timerId = timer.Register(func, 1000); // 定时一分钟响应回调,假设定时生效时间为0:00 +...... +/* +刚好在1:00触发事件删除,此时为临界状态,轮询线程如果提前于删除行为获取到响应事件,则会在unregister后发现响应事件被额外触发了一次,如果轮询线程响应晚于删除行为,则轮询线程不会感知该事件响应,unregister后对应事件不会额外触发一次 +*/ +timer.Unregister(timerId); + +``` + +2. 出于节省资源,提高性能的考虑,相同interval事件会复用timerFd,这可能导致部分事件响应时间与预期存在偏差,如果开发者对该响应时间有强要求,建议设置interval略带偏差(如1ms) + +```cpp +// 伪代码 +Timer timer("timer_test"); +CallBack func1; +CallBack func2; +timer.Setup(); + +// 假设起始定时器生效时间为0:00, 则func1的后续响应时间为1:00, 2:00, 3:00, 4:00...... +uint32_t timerId_1 = timer.Register(func1, 1000); // 定时一分钟循环响应回调 + +// 假设func2的定时起始时间为0:30, 原期望的后续响应为1:30, 2:30, 3:30, 4:30...... +// 但因timer fd复用,与func2共用timerfd,后续时间响应也与func1相同,即为1:00, 2:00, 3:00, 4:00...... +uint32_t timerId_2 = timer.Register(func2, 1000); // 定时一分钟循环响应回调,timerfd复用func1的timerfd + +// 假设func2的定时起始时间为0:30, 则func1的后续响应时间约(1ms偏差)为1:30, 2:30, 3:30, 4:30...... +uint32_t timerId_3 = timer.Register(func2, 1001); // 定时一分钟循环响应回调,timerfd不复用func1的timerfd +``` \ No newline at end of file -- Gitee