由实际开发中的idle的使用,产生困惑,继而理解AWTK的线程相关的模块设计
reference: AWStudio社区版 (zlg.cn)
thread src/tkc/thread.h
struct _tk_thread_t {
/* 平台无关成员(移植时必须定义) */
void* args; /* 线程回调函数上下文 */
tk_thread_entry_t entry; /* 线程回调函数 */
bool_t running; /* 线程是否正在执行 */
const char* name; /* 线程的名称 */
uint32_t stack_size; /* 线程的栈大小 */
uint32_t priority; /* 线程的优先级 */
/* 平台相关成员(移植到其他平台时,请根据实际需求定义) */
pthread_t thread; /* 此处以 pthread 为例 */
};
线程相关接口说明详见下表:
接口 | 说明 | 备注 |
---|---|---|
tk_thread_create | 创建thread对象 | 保存上下文 ctx 和回调函数 entry |
tk_thread_set_name | 设置线程名称 | |
tk_thread_set_stack_size | 设置线程栈大小 | |
tk_thread_set_priority | 设置线程优先级 | 某些平台可能不支持 |
tk_thread_get_priority_from_platform | 获取平台相关的优先级 | 某些平台可能不支持 |
tk_thread_start | 启动线程 | 将 running 属性设置为 TRUE |
tk_thread_join | 等待线程退出 | 将 running 属性设置为 FALSE |
tk_thread_get_args | 获取线程的参数 | |
tk_thread_destroy | 销毁thread对象 | |
tk_thread_self | 获取当前线程的原生句柄 |
awtk/src/tkc/mutex.h
struct _tk_mutex_t {
/* 定义一个互斥锁,请根据实际平台实现 */
pthread_mutex_t mutex; /* 此处以 pthread 为例 */
};
互斥锁相关接口详见下表:
接口 | 说明 |
---|---|
tk_mutex_create | 创建互斥锁对象 |
tk_mutex_lock | 加锁 |
tk_mutex_try_lock | 尝试加锁 |
tk_mutex_unlock | 解锁 |
tk_mutex_destroy | 销毁互斥锁对象 |
awtk/src/tkc/semaphore.h
struct _tk_semaphore_t {
/* 定义一个信号量,请根据实际平台实现 */
sem_t* sem; /* 此处以 sem_t 为例 */
};
信号量相关接口详见下表:
接口 | 说明 |
---|---|
tk_semaphore_create | 创建信号量对象 |
tk_semaphore_wait | 获取资源 |
tk_semaphore_post | 释放资源 |
tk_semaphore_destroy | 销毁信号量对象 |
awtk/src/tkc/cond.h
struct _tk_cond_t {
/* 定义一个条件变量,请根据实际平台实现 */
pthread_cond_t cond; /* 此处以 pthread 为例 */
};
条件变量相关接口详见下表:
接口 | 说明 |
---|---|
tk_cond_create | 创建条件变量对象 |
tk_cond_wait | 等待 |
tk_cond_wait_timeout | 等待指定时间 |
tk_cond_signal | 唤醒 |
tk_cond_destroy | 销毁条件变量对象 |
AWTK采取消息队列的方式来实现异步,使用的重要结构体如下:
typedef union _event_queue_req_t {
event_t event;
key_event_t key_event;
wheel_event_t wheel_event;
pointer_event_t pointer_event;
multi_gesture_event_t multi_gesture_event;
add_idle_t add_idle;
add_timer_t add_timer;
} event_queue_req_t;
GUI 控件只能在 GUI 线程进行操作,非 GUI 线程想操作 GUI 控件,必须用 idle_queue 或 timer_queue 进行串行化。
示例:
调用 tk_thread_create 创建一个线程,在线程的 test_timer_queue 回 调函数中调用 timer_queue 添加 timer 事件请求,在 timer 定时器中更新 label 文本框的内容。
static widget_t* s_label = NULL;
static ret_t update_label(int nr) {
if (s_label) {
char str[16] = {0};
tk_snprintf(str, sizeof(str), "%d", nr);
widget_set_text_utf8(s_label, str);
}
return nr > 0 ? RET_OK : RET_REMOVE;
}
/* GUI 线程中可调用与 GUI 相关的函数,如:widget_lookup */
static ret_t on_timer(const timer_info_t* timer) {
return update_label(*(int*)(timer->ctx));
}
/* 非 GUI 线程中不可调用与 GUI 相关的函数,如:widget_lookup */
void* test_timer_queue(void* args) {
static int nr = 500000;
while (nr-- > 0) {
timer_queue(on_timer, &nr, 30);
sleep_ms(30);
}
return NULL;
}
ret_t application_init() {
tk_thread_t* thread = NULL;
widget_t* win = window_create(NULL, 0, 0, 0, 0);
widget_t* label = label_create(win, 10, 10, 300, 20);
s_label = label;
thread = tk_thread_create(test_timer_queue, NULL);
tk_thread_start(thread);
return RET_OK;
}
ret_t application_exit() {
log_debug("application_exit\n");
return RET_OK;
}
#include "awtk_main.inc
**在上面的代码中,需要注意的是:如果在调用 tk_thread_create 函数创建线程的时候, 使用 label 对象作为线程的参数,并在 update_label 函数中使用该对象更新 label 文本框的内 容,会存在安全隐患。因为 label 是 GUI 界面中的一个文本框控件对象,在线程操作的过程 中,如果此时 label 对象在其他地方被销毁,再更新其文本内容就会出现问题。 **
需要注意的是:在非GUI线程中,不要调用操作GUI控件相关的函数,如:widget_lookup, 因为这些函数是非线程安全的。
捕获到系统设备事件并将其转化为 AWTK 支持的事件后,可以调用 AWTK 提供的以下接口将事件分发到 GUI 的主循环中处理:
函数名称 | 说明 | 备注 |
---|---|---|
main_loop_queue_event | 分发事件请求 | 可分发 AWTK 中的任意事件,需提供事件请求结构体 |
main_loop_post_key_event | 分发按键事件 | 需提供键值与按键状态 |
main_loop_post_pointer_event | 分发指针事件 | 需提供指针坐标与指针状态 |
main_loop_post_multi_gesture_event | 分发多点触摸事件 | 需提供多点触摸事件结构体 |
常见的输入设备事件分发方式有两种,分别是在 GUI 线程中分发以及在非 GUI 线程中分发,AWTK 为这两种方式分别提供了简单的实现,以方便用户移植。
1、在 GUI 线程中分发事件
对于裸系统平台,AWTK 提供了 main_loop_raw.inc,该文件实现了裸系统 main_loop 的基本功能,在移植时用户只需实现负责输入设备事件的分发 platform_disaptch_input 函数即可,该函数在 GUI 主循环中被调用,会阻塞主线程,示例代码如下:
/* 分发输入设备事件 */
ret_t platform_disaptch_input(main_loop_t *l) {
/* 捕获系统的触摸、鼠标、键盘等输入事件,并转化为 AWTK 支持的事件 */
event_queue_req_t req; /* AWTK 的事件请求 */
memset(&req, 0x00, sizeof(req));
req.xxx = system_get_input_event();
...
/* 调用接口将事件分发到 GUI 主循环 */
main_loop_queue_event(l, &req);
return RET_OK;
}
#include "main_loop/main_loop_raw.inc"
2、在非 GUI 线程中分发事件
在非 GUI 线程中获取并分发设备事件,不会阻塞主线程,可以在一定程度上提高 GUI 刷新效率。示例代码如下:
static void* input_run(void* ctx) {
main_loop_t* loop = (main_loop_t*)ctx;
while(1) {
/* 捕获系统的触摸、鼠标、键盘等输入事件,并转化为 AWTK 支持的事件 */
event_queue_req_t req; /* AWTK 的事件请求 */
memset(&req, 0x00, sizeof(req));
req.xxx = system_get_input_event();
...
/* 调用接口将事件分发到 GUI 主循环 */
main_loop_queue_event(loop, &req);
}
return NULL;
}
ret_t platform_disaptch_input(main_loop_t *l) {
if (run_once) { /* 保证仅执行一次,创建事件分发线程 */
system_create_thread(input_run);
}
}
#include "main_loop/main_loop_raw.inc"
reference:线程安全和可重入
不保护共享变量的函数。
i++操作
解决方法:对临界区加锁,或者使用PV操作的信号量来保护共享的变量。
保持跨越多个调用的状态函数。
伪随机数生成器,当调用srand为rand设置一个种子后,如果多线程调用rand函数,就会造成线程的安全隐患。
解决方法:重写rand函数,使得它不再使用任何static数据,而是依靠调用者在参数中传递状态信息。
返回指向静态变量的指针的函数。
比如将一个计算结果放在一个static变量中,然后返回一个指向这个变量的指针。如果多线程调用这些函数,正在被一个线程使用的结构会被另一个线程覆盖掉。
解决方法:① 选择重写函数,使得调用者传递存放结果的变量的地址,消除了所有共享数据。 ② 使用加锁-拷贝(lock-and-copy)技术。将线程不安全函数与互斥锁联系起来,在每一个调用位置,对互斥锁加锁,调用线程不安全函数,将函数返回的结果拷贝到一个私有的存储器位置,然后对互斥锁解锁。
调用线程不安全函数的函数。
我们假设函数A安全,函数B不安全。
情况①:如果函数A调用B,那么A不一定不安全。如果B是第2类的函数,即依赖于跨越多次调用的状态,那么A线程肯定不安全。
解决方法:对函数B进行重写。
情况②:如果B是第1类或者第3类。
解决方法:需要用互斥锁保护调用位置和任何得到的共享数据,A仍可能是线程安全的。
重入:即重复调用,函数被不同的流调用,有可能会出现第一次调用还没有返回时就再次进入该函数开始下一次调用。
可重入:当程序被多个线程反复执行,结果总是正确的。
不可重入:当程序被多个线程反复调用,产生的结果会出错。
可重入函数满足条件
很多时候,可重入函数与线程安全被用作同义词,但是它们还是有很明显的区别的,可重入函数仅仅是线程安全函数的一个真子集。
可重入函数与线程安全函数的区别