常见字符串函数在多线程场景下不安全的原因
- 全局或静态变量的使用:像
strtok
函数,它使用静态变量来保存字符串处理的上下文。在多线程环境下,多个线程同时调用 strtok
会共享这个静态变量,导致每个线程的处理结果相互干扰。例如,线程A正在处理一个字符串,线程B也调用 strtok
处理另一个字符串,线程B可能会修改线程A的上下文,使得线程A后续处理结果不正确。
- 不可重入性:许多C语言字符串函数不是可重入的,即它们在执行过程中可能会修改自身的状态,而不考虑多线程的影响。当多个线程同时调用这些函数时,会破坏函数执行的完整性,产生不确定的结果。
解决方案
- 互斥锁(Mutex)
- 原理:使用互斥锁来保护对字符串函数的调用。在调用字符串函数之前,获取互斥锁,确保同一时间只有一个线程能够进入临界区调用该函数,调用完成后释放互斥锁。
- 示例代码:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
pthread_mutex_t mutex;
void* thread_function(void* arg) {
char str[] = "hello,world";
char* token;
pthread_mutex_lock(&mutex);
token = strtok(str, ",");
while (token!= NULL) {
printf("Thread %lu: %s\n", (unsigned long)pthread_self(), token);
token = strtok(NULL, ",");
}
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&thread1, NULL, thread_function, NULL);
pthread_create(&thread2, NULL, thread_function, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
- 使用可重入版本的函数
- 原理:一些系统提供了可重入版本的字符串函数,如
strtok_r
。可重入版本的函数通过额外的参数来保存上下文,每个线程可以有自己独立的上下文,从而避免了共享静态变量带来的问题。
- 示例代码:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void* thread_function(void* arg) {
char str[] = "hello,world";
char* saveptr;
char* token = strtok_r(str, ",", &saveptr);
while (token!= NULL) {
printf("Thread %lu: %s\n", (unsigned long)pthread_self(), token);
token = strtok_r(NULL, ",", &saveptr);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_function, NULL);
pthread_create(&thread2, NULL, thread_function, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
- 线程本地存储(TLS)
- 原理:使用线程本地存储机制,为每个线程分配独立的存储空间来保存字符串函数需要的上下文信息。这样每个线程对字符串函数的调用都不会影响其他线程。
- 示例代码(以GCC的__thread关键字为例,不同编译器可能有不同实现):
#include <stdio.h>
#include <pthread.h>
#include <string.h>
__thread char savebuf[1024];
void* thread_function(void* arg) {
char str[] = "hello,world";
char* token = strtok_r(str, ",", savebuf);
while (token!= NULL) {
printf("Thread %lu: %s\n", (unsigned long)pthread_self(), token);
token = strtok_r(NULL, ",", savebuf);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_function, NULL);
pthread_create(&thread2, NULL, thread_function, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}