可重入函数未必是线程安全的;线程安全函数未必是可重入的。
可重入的程序(函数)允许在执行的过程中被打断,并在打断所执行的代码中再次安全的调用。
若一个函数是可重入的,则该函数应当满足下述条件:
总之一句话:上述条件就是要求可重入函数使用的所有变量都通过函数的调用者提供。
举例说明,设计一个发送字符串的状态机:
fsm_rt_t print_str(const char *pchStr) { static enum { START = 0, IS_END_OF_STRING, SEND_CHAR, } s_tState = START; static const char *s_pchStr = NULL; switch (s_tState) { case START: s_pchStr = pchStr; s_tState = IS_END_OF_STRING; //break; //!< fall-through case IS_END_OF_STRING: if (*s_pchStr == '\0') { PRINT_STR_RESET_FSM(); return fsm_rt_cpl; } s_tState = SEND_CHAR; //break; //!< fall-through case SEND_CHAR: if (serial_out(*s_pchStr)) { pchStr++; s_tState = IS_END_OF_STRING; } break; } return fsm_rt_on_going; }
由于状态机的中使用了静态变量,尤其是状态变量s_tState——这意味着同时执行的多个print_str,彼此共享同一个状态变量,它们是彼此干扰的。这意味着同时执行多个print_str是“不安全”的,是会出问题的(比如字符串长度不一致时很可能会出现buffer-overflow的问题),因此可以说 print_str 是不可重入的。
更改代码后:
#undef this #define this (*ptThis) #define PRINT_STR_RESET_FSM() \ do { this.State = START; } while(0) typedef struct print_str_t { uint8_t chState; const char *pchStr; } print_str_t; fsm_rt_t print_str(print_str_t *ptThis, const char *pchStr) { enum { START = 0, IS_END_OF_STRING, SEND_CHAR, }; switch (this.chState) { case START: this.pchStr = pchStr; this.chState = IS_END_OF_STRING; //break; //!< fall-through case IS_END_OF_STRING: if (*(this.pchStr) == '\0') { PRINT_STR_RESET_FSM(); return fsm_rt_cpl; } this.chState = SEND_CHAR; //break; //!< fall-through case SEND_CHAR: if (serial_out(*(this.pchStr))) { this.pchStr++; this.chState = IS_END_OF_STRING; } break; } return fsm_rt_on_going; }
此状态机所使用的所有变量,都有状态机控制块print_str_t 提供,也就是函数的调用者提供的,所以是可重入的。
状态机print_str使用了共享函数serial_out(),它是一个临界资源,当该状态机存在多个实例时,必然都会访问这个临界资源,从而导致打印出来的数据不是自己想要的,所以此状态机不是线程安全的。
线程安全指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享资源,使程序(函数)都能 给出正确的结果。
举例说明:
#include <pthread.h> int increment_counter () { static int counter = 0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); // only allow one thread to increment at a time ++counter; // store value before any other threads increment it further int result = counter; pthread_mutex_unlock(&mutex); return result; }
上面的代码中,函数increment_counter可以在多个线程中被调用,因为有一个互斥锁mutex来同步对共享变量counter的访问。但是如果这个函数用在可重入的中断处理程序中,如果在pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)之间产生另一个调用函数increment_counter的中断,则会第二次执行此函数,此时由于mutex已被lock,函数会在pthread_mutex_lock(&mutex)处阻塞,并且由于mutex没有机会被unlock,阻塞会永远持续下去。简言之,问题在于 pthread 的 mutex 不可重入。
可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有重大区别: