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

函数执行结果如下:


iOS 等待主线程空闲 主线程等待子线程_数据

这段代码需要好好分析才能看懂它的打印结果,但是实际上它的逻辑不是很复杂。由于操作系统调度的原因,同样的代码打印结果顺序可能有点不一样也是正常的。关于线程,还有非常重要的一点就是线程的同步,这个到下一节再讲。