pthread遇到的问题

最近在写LOGO检测的程序,为了提高速度,使用了pthread进行多线程处理,单次处理一次图片没有问题,可以分配到多核进行并行处理,但处理一组图片时总是出错。于是开始分析问题的所在,最后发现是参数传递到pthread函数中时出现了问题。

pthread也叫做POSIX thread,是多线程编程的集合。使用它编程比较简单,在需要并行处理的地方调用pthread_create()函数,然后使用pthread_join()函数等待线程结束即可。

pthread_create函数如下:

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

其中,pthread_t表示了这个线程的对象;pthread_attr_t表示了应用在这个线程上的属性,一般设为NULL;接下来的那个参数就是要多线程执行的函数,这个函数一般定义为void* func_name(void* para);最后一个是传入前面这个函数的参数,也是我遇到问题的地方。

在我的程序中,要对一组特征点进行计算,但各个特征点的计算相互之间没有影响,于是我定义了一个struct结构体往多线程函数中传递参数,每次启动多个线程,每个线程负责一部分的特征点计算。但传进去的参数往往会出现两三组是一样的结果,于是开始查找原因所在。

stackoverflow上的这个解答比较详细,出现这个问题的原因在于:使用for循环启动多线程,而传入的参数在for循环内使用的是同一个参数,而同一个参数会存在同一处内存中,我们无法确定各个线程启动的快慢,这样在线程启动时,也许参数还没更新,或者已经更新过了几次才启动线程,这就导致我遇到的这个问题。而解决方法就是使用malloc分配参数,这样for中的每次循环都会生成一个新的参数,即内存地址不相同,就可以解决这个问题了。

为了详细说明,我简单写了个测试代码。

#include <pthread.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

typedef struct thread_info {
    int total;
    long rank;
}thread_info;

void* thread_test(void* para) {
    struct thread_info* p_info = (struct thread_info*)para;

    long rank = p_info->rank;
    std::cout<<"in thread "<<rank<<", address "<<p_info<<std::endl;

    return NULL;
}

void* thread_test1(void* para) {
    struct thread_info* p_info = (struct thread_info*)para;

    long rank = p_info->rank;
    std::cout<<"in thread "<<rank<<", address "<<p_info<<std::endl;

    free(para);
    return NULL;
}


int main(void) {

    pthread_t pthread_id[4];

    std::cout<<"test struct"<<std::endl;
    for (long i=0; i<4; ++i) {
        struct thread_info p_info;
        p_info.total = 20;
        p_info.rank = i;
        pthread_create(&pthread_id[i], NULL, thread_test, (void*)&p_info);
    }

    for (long i=0; i<4; ++i)
        pthread_join(pthread_id[i], NULL);

    std::cout<<std::endl;
    std::cout<<"test malloc"<<std::endl;

    for (long i=0; i<4; ++i) {
        struct thread_info *p_info = (struct thread_info*)malloc(sizeof(struct thread_info*));
        p_info->total = 20;
        p_info->rank = i;
        pthread_create(&pthread_id[i], NULL, thread_test1, (void*)p_info);
    }

    for (long i=0; i<4; ++i)
        pthread_join(pthread_id[i], NULL);

    return 0;
}

这段代码首先定义了一个struct thread_info的结构体,其中的totalrank就是要传入多线程函数void* thread_test(void* para)void* thread_test1(void* para)的参数。在main()函数中,使用pthread_t定义了要启动的线程数,接下来使用for循环来创建多线程。第一个for循环直接定义了一个struct thread_info p_info变量,第二个for循环使用了malloc来创建参数。每次使用pthread_create函数时,都要使用pthread_join函数等待线程执行结束,否则线程不会停止退出。

使用g++ -o test_pthread test_pthread.cpp -lpthread进行编译,然后运行可以看到:

$ ./test_pthread 
test struct
in thread 2, address 0x7fff02494560
in thread 3, address 0x7fff02494560
in thread 3, address 0x7fff02494560
in thread 3, address 0x7fff02494560

test malloc
in thread 0, address 0xb604d0
in thread 1, address 0xb604f0
in thread 3, address 0xb60530
in thread 2, address 0xb60510

从结果可以看到,直接定义的参数在传入到多线程函数里面后的地址是一样的,而四个线程中有3个执行了相同的内容,显然不是我想要的;而第二个使用malloc定义的参数一切正常,传入的参数地址不相同,每个线程执行的内容也不相同。从结果中也可以看到,线程并没有按顺序启动。对了,使用了malloc,就不要忘了free

有意思的是,当传入的参数是int时也不会出现什么问题,暂且记录下来,以后再深入了解。

对于多线程编程,还要考虑是否线程安全,一般加锁就可以解决,如果需要更细粒度的,也可以在结构体中添加锁,具体使用方法可以谷歌一下,我暂时还没遇到锁相关的问题,也就没什么可说的了。