1 全局调用一次curl_global_init
CURLcode curl_global_init(long flags); 在多线程应用中,需要在主线程中调用这个函数。这个函数设置libcurl所需的环境。通常情况,如果不显式的调用它,第一次调用curl_easy_init()时,curl_easy_init 会调用 curl_global_init,在单线程环境下,这不是问题。但是多线程下就不行了,因为curl_global_init不是线程安全的。在多个线程中调用curl_easy_int,然后如果两个线程同时发现curl_global_init还没有被调用,同时调用curl_global_init,悲剧就发生了。这种情况发生的概率很小,但可能性是存在的。
2 CURLOPT_NOSIGNAL 选项设置
libcurl 有个很好的特性,它甚至可以控制域名解析的超时。但是在默认情况下,它是使用alarm + siglongjmp 实现的。用alarm在多线程下做超时,本身就几乎不可能。如果只是使用alarm,并不会导致程序崩溃,但是,再加上siglongjmp,就要命了(程序崩溃的很可怕,core中几乎看不出有用信息),因为其需要一个sigjmp_buf型的全局变量,多线程修改它。(通常情况下,可以每个线程一个 sigjmp_buf 型的变量,这种情况下,多线程中使用 siglongjmp 是没有问题的,但是libcurl只有一个全局变量,所有的线程都会用)。具体是类似 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L) 的超时设置,导致alarm的使用(估计发生在域名解析阶段),如前所述,这在多线程中是不行的。解决方式是禁用掉alarm这种超时, curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)
3 使用openssl 注意加锁
openssl是不支持多线程的,而libcurl中并没有支持相关的加锁操作,要自己做加锁的处理
1 在初始化libcurl的时候为openssl创建一个互斥锁函数,一个回调函数传给openssl
openssl锁函数原形 :void (* func )(int ,int , const char * ,int)
设置方式:CRYPTO_set_locking_callback(void (* func )(int ,int , const char * ,int));
2 设置这样一个函数还不够,另外还要配置一个锁id回调函数,这个可以参考openssl多线程下的使用相关。
id函数原形:unsigned int (*func)(void)
设置方式:CRYPTO_set_id_callback(unsigned int (*func)(void));
通过这两个设置就可以解决HTTPS多线程请求出现crash的问题。
然后在初始化libcurl的地方调用init_locks初始化锁;最后退出时,调用kill_locks去初始化。
4 参考代码
#if defined __GNUC__ || defined LINUX
static pthread_mutex_t* lockarray;
#else
static HANDLE* lockarray;
#endif
static void lock_callback(int mode, int type, char *file, int line)
{
(void)file;
(void)line;
if (mode & CRYPTO_LOCK) {
#if defined __GNUC__ || defined LINUX
pthread_mutex_lock(&(lockarray[type]));
#else
WaitForSingleObject(lockarray[type], INFINITE);
#endif
}
else
{
#if defined __GNUC__ || defined LINUX
pthread_mutex_unlock(&(lockarray[type]));
#else
ReleaseMutex(lockarray[type]);
#endif
}
}
static unsigned long thread_id(void)
{
unsigned long ret;
#if defined __GNUC__ || defined LINUX
ret = (unsigned long)pthread_self();
#else
ret = (unsigned long)GetCurrentThreadId();
#endif
return(ret);
}
static void init_locks(void)
{
int i;
#if defined __GNUC__ || defined LINUX
lockarray = (pthread_mutex_t*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
for (i=0; i<CRYPTO_num_locks(); i++)
{
pthread_mutex_init(&(lockarray[i]), NULL);
}
#else
lockarray = (HANDLE *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(HANDLE));
for (i=0; i<CRYPTO_num_locks(); i++)
{
lockarray[i] = CreateMutexA(NULL, false, "");
}
#endif
CRYPTO_set_id_callback((unsigned long (*)())thread_id);
CRYPTO_set_locking_callback((void (*)(int, int, const char*, int))lock_callback);
}
static void kill_locks(void)
{
int i;
CRYPTO_set_locking_callback(NULL);
for (i=0; i<CRYPTO_num_locks(); i++)
{
#if defined __GNUC__ || defined LINUX
pthread_mutex_destroy(&(lockarray[i]));
#else
CloseHandle(lockarray[i]);
#endif
}
OPENSSL_free(lockarray);
}