Bootstrap

libcurl 多线程使用

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);

}
;