int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
这个函数用来创建一个线程,一共有四个参数,第一个参数是线程的标识符,和进程一样,每个线程都有自己的标识符,这是一个输出型参数。第二个参数是线程的属性(线程的属性后面再分析,一般默认即可),第三个参数是一个函数指针,就是创建线程之后这个线程跳到哪里去执行。第四个参数是给第三个参数里的函数指针传参的,因为第三个参数,也就是start_routine它里面的参数只有一个void*,如果要给这个函数指针指向的函数传参,就要通过arg参数,如果有多个参数,可以用结构体封装,然后把结构体的地址放在arg处,最后就会传给start_routine的参数void * 。如果要获取线程自身的标识符,可以使用函数:
pthread_t pthread_self(void);
如果要比较两个线程的标识符是否相等,可以使用函数:
int pthread_equal(pthread_t tid1,pthread_t tid2);
如果相等返回一个非0值,否则返回一个0值。另外需要注意的就是编译和线程相关的代码,需要在gcc后加上-l选项,指明要链接的库
gcc test.c -o test -lpthread
2、线程的退出:
void pthread_exit(void*retval);
无返回值,参数retval是线程退出时的状态,其实就是用户传给这个函数的一个参数,或者说提示信息。线程的阻塞:
int pthread_join(pthread_t thread, void **retval);
这个函数是等待回收某个线程,和进程里面的waitpid很相似,第一个参数是线程的标识符,表示要等待回收哪个线程,第二个参数是接收线程退出时的状态,就是上面的retval。成功返回0,失败返回其他值。为什么要等待回收呢?比如主线程创建了一个子线程,然后子线程去完成一些事情,如果没有等待回收,那么很可能子线程还没有做完它的事情,主线程就退出了,而主线程一退出,子线程就会被强制退出,这不是我们想要的,所以主线程需要等待回收子线程,然后主线程再退出,子线程还没有退出的时候,主线程就会被阻塞。3、取消和清理线程:
int pthread_cancel(pthread_t thread);
这个函数用来取消同一进程中的其他线程。
void pthread_cleanup_push(void (*routine)(void *)void pthread_cleanup_pop(int execute);
这两个函数是线程的清理处理。好像用的不多。4、分离线程:
int pthread_detach(pthread_t thread);
在Linux中,线程一般有分离和非分离的状态,在默认情况下是非分离的状态,父线程维护子线程的某些信息并等待子线程的退出,如果没有显式调用join函数,子线程退出时父线程维护的信息可能没有得到及时的释放,继而导致堆栈空间不足。而对于分离状态的线程来说,线程结束后,不会有其他的线程等待它,资源会及时释放。要使线程分离,可以调用上面的函数,成功返回0,失败返回错误编号。5、线程属性在第一个线程创建的函数里面,第二个参数是线程的属性,我们一般填入NULL,这样就是采用默认的属性,但是如果我们希望对线程的属性进行设置,也是可以的。属性的初始化和销毁:
int pthread_attr_init(pthread_attr_t *attr);int pthread_attr_destroy(pthread_attr_t *attr);
首先要调用初始化函数,设置完属性后要调用销毁函数。它里面只有一个参数,是一个属性的对象,我们需要先定义这样一个对象(或者说变量),然后把它的地址放进去就行了。初始化完了之后就可以对这个属性对象进行设置了,比如,可以设置属性对象的分离状态
pthread_t threadid;pthread_attr_t thread_attr;pthread_attr_init(&thread_attr);pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);pthread_create(&threadid,&thread_attr,threaddeal,NULL);
这样创建出来的线程就是不是默认状态的非分离,而是分离的了。当然,还有很多其他的属性可以设置,比如设置堆栈大小,堆栈地址,作用域,继承调度,调度策略,调度参数等,这里就不展开了,一般使用默认的就好了。6、线程的私有数据(难点)每个线程都有一些属于自己的数据,当线程对这些数据进行操作的时候可以独立地访问他们,而不用担心其他线程和自己争夺所有权,这种数据被称为线程的私有数据。在分配私有数据之前,需要创建一个键(key),通过这个键才能获取对线程私有数据的访问权。
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));
这个函数的第一个参数就是返回一个key,它是一个输出型参数,第二个参数是一个函数指针,它是一个析构函数,当线程退出的时候,如果数据地址已经被置为一个非NULL的值,它会自动跳转到这里去执行,和C++中的析构函数很相似。如果要取消键关联,可以调用下面的函数
int pthread_key_delete(pthread_key_t key);
线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与之关联,各个键的析构函数可以不同。有些线程可能看到某个键值,而其他的线程可能看到一个不同的值,这是一种竞争,如果要解决这种进程,可以使用pthread_once函数
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
这个函数可以保证某些初始化代码至多只能被执行一次,第一个参数指向静态的或外部的变量,初始化为PTHREAD_ONCE_INIT。比如我们可以这样写:
pthread_once_t once_control = PTHREAD_ONCE_INIT;pthread_once(&once_control,fun);
这可以保证fun只被执行一次。在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。接下来就是键关联函数:
int pthread_setspecific(pthread_key_t key, const void *pointer);
这个就是将键值和线程私有数据的地址绑定起来,绑定之后,就可以通过键值来获取私有数据的地址了。私有数据地址获取函数:
void * pthread_getspecific(pthread_key_t key);
这个就是通过键值来获取私有数据的地址,如果没有私有数据和键关联,那么就会返回NULL,否则返回私有数据的地址。接下来写一个代码,主线程先创建一个键key,然后通过key访问自己的私有数据,然后创建两个子线程,子线程用同样的key去访问私有数据,虽然key相同,但是子线程访问的依然是自己的私有数据,而不是主线程的数据。程序大致的逻辑就是,主线程先创建一个key,然后检查这个key有没有绑定某个地址,没有就用malloc函数开辟一段内存,然后将这个key和这个地址绑定起来,之后就可以通过这个key访问自己的私有数据了。子线程用同样的key,去检查key有没有绑定地址,会发现没有绑定,这是因为刚刚绑定的是主线程的私有数据的地址,现在子线程当然是没有绑定的。子线程也用malloc函数开辟一段内存,将这段内存和key绑定起来,之后就可以通过key来访问自己的私有数据了。
#include#include#include#includestatic pthread_key_t str_key;static pthread_once_t str_alloc_key_once=PTHREAD_ONCE_INIT;static void str_alloc_key();static void str_alloc_destroy_accu(void *accu);char* str_accumulate(const char* s){ char* accu; pthread_once(&str_alloc_key_once,str_alloc_key); accu=(char*)pthread_getspecific(str_key); if(!accu) { accu=malloc(1024); if(!accu){ perror("malloc failed"); return NULL; } pthread_setspecific(str_key,(void*)accu); printf("Thread %lx allocating buffer at %p\n",pthread_self(),accu); } strcat(accu,s); return accu;}static void str_alloc_key(){ pthread_key_create(&str_key,str_alloc_destroy_accu); printf("Thread %lx:alloced key %d\n",pthread_self(),str_key);}static void str_alloc_destroy_accu(void *accu){ printf("Thread %lx :freeing buffer at %p\n",pthread_self(),accu); free(accu);}void *threadDeal(void* arg){ char* str; str=str_accumulate("Result of "); str=str_accumulate((char*)arg); str=str_accumulate(" thread"); printf("Thread %lx :\"%s\"\n",pthread_self(),str); return NULL;}int main(int argc,char* argv[]){ char *str; pthread_t th1,th2; str=str_accumulate("Result of "); pthread_create(&th1,NULL,threadDeal,(void*)"first"); pthread_create(&th2,NULL,threadDeal,(void*)"second"); str=str_accumulate("initial thread"); printf("Thread %lx %s \n",pthread_self(),str); pthread_join(th1,NULL); pthread_join(th2,NULL); return 0;}
函数执行结果如下:
这段代码需要好好分析才能看懂它的打印结果,但是实际上它的逻辑不是很复杂。由于操作系统调度的原因,同样的代码打印结果顺序可能有点不一样也是正常的。