pthread 介绍

进行孤儿与僵尸

1- 应用层pthread

#include
引用连接libpthread.so这个库,因此在程序链接阶段应该有类似
gcc program.o -o program -lpthread

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
函数参数:

  • 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。
  • 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。如果线程创建成功,这个接口会返回0。
  • 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。
  • 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。

pthread_create例子

#include   
#include   
void* thread( void *arg )  
{  
    printf( "This is a thread and arg = %d.\n", *(int*)arg);  
    *(int*)arg = 0;  
    return arg;  
}  
int main( int argc, char *argv[] )  
{  
    pthread_t th;  
    int ret;  
    int arg = 10;  
    int *thread_ret = NULL;  
    ret = pthread_create( &th, NULL, thread, &arg );  
    if( ret != 0 ){  
        printf( "Create thread error!\n");  
        return -1;  
    }  
    printf( "This is the main process.\n" );  
    pthread_join( th, (void**)&thread_ret );  
    printf( "thread_ret = %d.\n", *thread_ret );  
    return 0;  
}

######程序执行结果:
This is the main process.
This is a thread and arg = 10.
thread_ret = 0.  

pthread_join()接口会阻塞主进程的执行,直到合并的线程执行结束

2- pthread 合并与分离

pthread_detach 线程分离
pthread_join 线程合并

2.1 pthread_join
线程的合并是一种主动回收线程资源的方案。当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。

2.2 pthread_detach
线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。

线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。

3-线程的属性

int pthread_attr_init(pthread_attr_t *attr);  
int pthread_attr_destory(pthread_attr_t *attr);  

Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性。

3.1 绑定属性
说到这个绑定属性,就不得不提起另外一个概念:轻进程(Light Weight Process,简称LWP)。

轻进程和Linux系统的内核线程拥有相同的概念,属于内核的调度实体。一个轻进程可以控制一个或多个线程。
在计算机操作系统中,轻量级进程(LWP)是一种实现多任务的方法。与普通进程相比,LWP与其他进程共享所有(或大部分)它的逻辑地址空间和系统资源;与线程相比,LWP有它自己的进程标识符,并和其他进程有着父子关系;这是和类Unix操作系统的系统调用vfork()生成的进程一样的。另外,线程既可由应用程序管理,又可由内核管理,而LWP只能由内核管理并像普通进程一样被调度。Linux内核是支持LWP的典型例子。

默认情况下,对于一个拥有n个线程的程序,启动多少轻进程,由哪些轻进程来控制哪些线程由操作系统来控制,这种状态被称为非绑定的。那么绑定的含义就很好理解了,只要指定了某个线程“绑”在某个轻进程上,就可以称之为绑定的了。

被绑定的线程具有较高的相应速度,因为操作系统的调度主体是轻进程,绑定线程可以保证在需要的时候它总有一个轻进程可用。绑定属性就是干这个用的。

设置绑定属性的接口是pthread_attr_setscope(),它的完整定义是:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);

它有两个参数,第一个就是线程属性对象的指针,第二个就是绑定类型,拥有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)
例子:

#include   
#include   
……  
int main( int argc, char *argv[] )  
{  
    pthread_attr_t attr;  
    pthread_t th;  
    ……  
    pthread_attr_init( &attr );  
    pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
    pthread_create( &th, &attr, thread, NULL );  
    ……  
}  

Linux并不支持线程的非绑定,为了兼容设计了该接口!

3.2 分离属性
前面说过线程能够被合并和分离,分离属性就是让线程在创建之前就决定它应该是分离的。如果设置了这个属性,就没有必要调用pthread_join()或pthread_detach()来回收线程资源了。
设置分离属性的接口是pthread_attr_setdetachstate(),它的完整定义是:
pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
它的第二个参数有两个取值:PTHREAD_CREATE_DETACHED(分离的)和PTHREAD_CREATE_JOINABLE(可合并的,也是默认属性)。
例子:分离属性

#include   
#include   
……  
int main( int argc, char *argv[] )  
{  
    pthread_attr_t attr;  
    pthread_t th;  
    ……  
    pthread_attr_init( &attr );  
    pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
    pthread_create( &th, &attr, thread, NULL );  
    ……  
} 

3.3调度属性
线程的调度属性有三个,分别是:算法、优先级和继承权。

3.3.1 算法
Linux提供的线程调度算法有三个:轮询、先进先出和其它。
其中轮询和先进先出调度算法是POSIX标准所规定,而其他则代表采用Linux自己认为更合适的调度算法,所以默认的调度算法也就是其它了。
轮询和先进先出调度算法都属于实时调度算法。

  • 轮询指的是时间片轮转,当线程的时间片用完,系统将重新分配时间片,并将它放置在就绪队列尾部,这样可以保证具有相同优先级的轮询任务获得公平的CPU占用时间;
  • 先进先出就是先到先服务,一旦线程占用了CPU则一直运行,直到有更高优先级的线程出现或自己放弃。

pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
第二个参数有三个取值:SCHED_RR(轮询)、SCHED_FIFO(先进先出)和SCHED_OTHER(其它)

3.3.2优先级
Linux的线程优先级是从1到99的数值,数值越大代表优先级越高。
而且要注意的是,只有采用SHCED_RR或SCHED_FIFO调度算法时,优先级才有效。对于采用SCHED_OTHER调度算法的线程,其优先级恒为0。

pthread_attr_setschedparam:

struct sched_param {
    int sched_priority;
}
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);  

param 这个参数就是设定优先级的参数
此外,即便采用SCHED_RR或SCHED_FIFO调度算法,线程优先级也不是随便就能设置的。首先,进程必须是以root账号运行的;其次,还需要放弃线程的继承权。什么是继承权呢?

3.3.3继承权
继承权就是当创建新的线程时,新线程要继承父线程(创建者线程)的调度属性。如果不希望新线程继承父线程的调度属性,就要放弃继承权。(新线程在默认情况下是拥有继承权)
设置线程继承权的接口是pthread_attr_setinheritsched(),它的完整定义是:
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
inheritsched参数有两个取值:
PTHREAD_INHERIT_SCHED(拥有继承权)
PTHREAD_EXPLICIT_SCHED(放弃继承权)

设置线程调度属性:

#include   
#include   
#include   
#include   
#define THREAD_COUNT 12  
void show_thread_policy( int threadno )  
{  
    int policy;  
    struct sched_param param;  
    pthread_getschedparam( pthread_self(), &policy, param );  
    switch( policy ){  
    case SCHED_OTHER:  
        printf( "SCHED_OTHER %d\n", threadno );  
        break;  
    case SCHED_RR:  
        printf( "SCHDE_RR %d\n", threadno );  
        break;  
    case SCHED_FIFO:  
        printf( "SCHED_FIFO %d\n", threadno );  
        break;  
    default:  
        printf( "UNKNOWN\n");  
    }  
}  
void* thread( void *arg )  
{  
    int i, j;  
    long threadno = (long)arg;  
    printf( "thread %d start\n", threadno );  
    sleep(1);  
    show_thread_policy( threadno );  
    for( i = 0; i < 10; ++i ) {  
        for( j = 0; j < 100000000; ++j ){}  
        printf( "thread %d\n", threadno );  
    }  
    printf( "thread %d exit\n", threadno );  
    return NULL;  
}  
int main( int argc, char *argv[] )  
{  
    long i;  
    pthread_attr_t attr[THREAD_COUNT];  
    pthread_t pth[THREAD_COUNT];  
    struct sched_param param;  
    for( i = 0; i < THREAD_COUNT; ++i )  
        pthread_attr_init( &attr[i] );  
        for( i = 0; i < THREAD_COUNT / 2; ++i ) {  
            param.sched_priority = 10;                    
            pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
            pthread_attr_setschedparam( &attr[i], param );  
            pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
        }  
        for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {  
            param.sched_priority = 20;                    
            pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
            pthread_attr_setschedparam( &attr[i], param );  
            pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
        }  
        for( i = 0; i < THREAD_COUNT; ++i )                      
            pthread_create( &pth[i], &attr[i], thread, (void*)i );                
        for( i = 0; i < THREAD_COUNT; ++i )
            pthread_join( pth[i], NULL );
        for( i = 0; i < THREAD_COUNT; ++i ) 
            pthread_attr_destroy( &attr[i] );              
    return 0;                             
} 

3.4堆栈大小属性
从前面的这些例子中可以了解到,线程的主函数与程序的主函数main()有一个很相似的特性,那就是可以拥有局部变量。虽然同一个进程的线程之间是共享内存空间的,但是它的局部变量确并不共享。原因就是局部变量存储在堆栈中,而不同的线程拥有不同的堆栈。Linux系统为每个线程默认分配了8MB的堆栈空间,如果觉得这个空间不够用,可以通过修改线程的堆栈大小属性进行扩容。

修改线程堆栈大小属性的接口是pthread_attr_setstacksize(),它的完整定义为:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

它的第二个参数就是堆栈大小了,以字节为单位。需要注意的是,线程堆栈不能小于16KB,而且尽量按4KB(32位系统)或2MB(64位系统)的整数倍分配,也就是内存页面大小的整数倍。此外,修改线程堆栈大小是有风险的,如果你不清楚你在做什么,最好别动它.

3.5满栈警戒区属性
既然线程是有堆栈的,而且还有大小限制,那么就一定会出现将堆栈用满的情况。线程的堆栈用满是非常危险的事情,因为这可能会导致对内核空间的破坏,一旦被有心人士所利用,后果也不堪设想。为了防治这类事情的发生,Linux为线程堆栈设置了一个满栈警戒区。这个区域一般就是一个页面,属于线程堆栈的一个扩展区域。一旦有代码访问了这个区域,就会发出SIGSEGV信号进行通知。

虽然满栈警戒区可以起到安全作用,但是也有弊病,就是会白白浪费掉内存空间,对于内存紧张的系统会使系统变得很慢。所有就有了关闭这个警戒区的需求。同时,如果我们修改了线程堆栈的大小,那么系统会认为我们会自己管理堆栈,也会将警戒区取消掉,如果有需要就要开启它。
修改满栈警戒区属性的接口是pthread_attr_setguardsize(),它的完整定义为:
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

它的第二个参数就是警戒区大小了,以字节为单位。与设置线程堆栈大小属性相仿,应该尽量按照4KB或2MB的整数倍来分配。当设置警戒区大小为0时,就关闭了这个警戒区。

虽然栈满警戒区需要浪费掉一点内存,但是能够极大的提高安全性,所以这点损失是值得的。而且一旦修改了线程堆栈的大小,一定要记得同时设置这个警戒区。

4-线程本地存储

线程局部变量,线程的其他函数难以访问, 全局变量可以解决问题(有弊端). 因此引入TLS 概念(不同线程有独立的存储空间)。
(C程序库中的errno是个最典型的一个例子。errno是一个全局变量,会保存最后一个系统调用的错误代码。在单线程环境并不会出现什么问题。但是在多线程环境,由于所有线程都会有可能修改errno,这就很难确定errno代表的到底是哪个系统调用的错误代码了。这就是有名的“非线程安全(Non Thread-Safe)”的。)

TLS:

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));  
int pthread_key_delete(pthread_key_t key);  
void* pthread_getspecific(pthread_key_t key);  
int pthread_setspecific(pthread_key_t key, const void *value); 
##==============================================
    pthread_key_create()接口用于创建一个线程本地存储区。 
    - key : 第一个参数用来返回这个存储区的句柄,需要使用一个全局变量保存,以便所有线程都能访问到。 
    - *destructor : 第二个参数是线程本地数据的回收函数指针,如果希望自己控制线程本地数据的生命周期,这个参数可以传递NULL。

    pthread_key_delete()接口用于回收线程本地存储区。其唯一的参数就要回收的存储区的句柄。

    pthread_getspecific()和pthread_setspecific()这个两个接口分别用于获取和设置线程本地存储区的数据。这两个接口在不同的线程下会有不同的结果不同(相同的线程下就会有相同的结果),这也就是线程本地存储的关键所在。

使用线程本地存储

#include   
#include   
#include   
#define THREAD_COUNT 10  
pthread_key_t g_key;  
typedef struct thread_data{  
    int thread_no;  
} thread_data_t;  
void show_thread_data()  
{  
    thread_data_t *data = pthread_getspecific( g_key );  
    printf( "Thread %d \n", data->thread_no );  
}  
void* thread( void *arg )  
{  
    thread_data_t *data = (thread_data_t *)arg;  
    printf( "Start thread %d\n", data->thread_no );  
    pthread_setspecific( g_key, data );  
    show_thread_data();  
    printf( "Thread %d exit\n", data->thread_no );  
}  
void free_thread_data( void *arg )  
{  
    thread_data_t *data = (thread_data_t*)arg;  
    printf( "Free thread %d data\n", data->thread_no );  
    free( data );  
}  
int main( int argc, char *argv[] )  
{  
    int i;  
    pthread_t pth[THREAD_COUNT];  
    thread_data_t *data = NULL;  
    pthread_key_create( &g_key, free_thread_data );  
    for( i = 0; i < THREAD_COUNT; ++i ) {  
        data = malloc( sizeof( thread_data_t ) );  
        data->thread_no = i;  
        pthread_create( &pth[i], NULL, thread, data );  
    }  
    for( i = 0; i < THREAD_COUNT; ++i )  
        pthread_join( pth[i], NULL );  
    pthread_key_delete( g_key );  
    return 0;  
} 

5-线程同步

Linux提供的线程同步机制主要有互斥锁和条件变量
5.1 互斥锁
所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程.

从互斥锁的这种行为看,线程加锁和解锁之间的代码相当于一个独木桥,同意时刻只有一个线程能执行。从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  
int pthread_mutex_destory(pthread_mutex_t *mutex );  
int pthread_mutex_lock(pthread_mutex_t *mutex);  
int pthread_mutex_trylock(pthread_mutex_t *mutex);  
int pthread_mutex_unlock(pthread_mutex_t *mutex);  

从这些定义中可以看到,互斥锁也是有属性的。只不过这个属性在绝大多数情况下都不需要改动,所以使用默认的属性就行。方法就是给它传递NULL。

phtread_mutex_trylock()比较特别,用它试图加锁的线程永远都不会被系统睡眠,只是通过返回EBUSY来告诉程序员这个锁已经有人用了。至于是否继续“强闯”临界区,则由程序员决定。系统提供这个接口的目的可不是让线程“强闯”临界区的。它的根本目的还是为了提高并行性,留着这个线程去干点其它有意义的事情。当然,如果很幸运恰巧这个时候还没有人拥有这把锁,那么自然也会取得临界区的使用权。

使用互斥锁:

#include   
#include   
#include   
#include   
#include   
pthread_mutex_t g_mutex;  
int g_lock_var = 0;  
void* thread1( void *arg )  
{  
    int i, ret;  
    time_t end_time;  
    end_time = time(NULL) + 10;  
    while( time(NULL) < end_time ) {  
        ret = pthread_mutex_trylock( &g_mutex );  
        if( EBUSY == ret ) {  
            printf( "thread1: the varible is locked by thread2.\n" );  
        } else {  
            printf( "thread1: lock the variable!\n" );  
            ++g_lock_var;  
            pthread_mutex_unlock( &g_mutex );  
        }  
        sleep(1);  
    }  
    return NULL;  
}  
void* thread2( void *arg )  
{  
    int i;  
    time_t end_time;  
    end_time = time(NULL) + 10;  
    while( time(NULL) < end_time ) {  
        pthread_mutex_lock( &g_mutex );  
        printf( "thread2: lock the variable!\n" );  
        ++g_lock_var;  
        sleep(1);  
        pthread_mutex_unlock( &g_mutex );  
    }  
    return NULL;  
}  
int main( int argc, char *argv[] )  
{  
    int i;  
    pthread_t pth1,pth2;  
    pthread_mutex_init( &g_mutex, NULL );  
    pthread_create( &pth1, NULL, thread1, NULL );  
    pthread_create( &pth2, NULL, thread2, NULL );  
    pthread_join( pth1, NULL );  
    pthread_join( pth2, NULL );  
    pthread_mutex_destroy( &g_mutex );  
    printf( "g_lock_var = %d\n", g_lock_var );  
    return 0;                              
}  

互斥锁在同一个线程内,没有互斥的特性,要避免死锁的发生

5.2 条件变量
条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);  
int pthread_cond_destory(pthread_cond_t *cond);  
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const timespec *abstime);  
int pthread_cond_signal(pthread_cond_t *cond);  
int pthread_cond_broadcast(pthread_cond_t *cond); 

对于等待“事件”的接口从其名称中可以看出,一种是无限期等待,一种是限时等待。后者与互斥锁的pthread_mutex_trylock()有些类似,即当等待的“事件”经过一段时间之后依然没有发生,那就去干点别的有意义的事情去。

而对于控制“事件”发生的接口则有“单播”和“广播”之说。所谓单播就是只有一个线程会得到“事件”已经发生了的“通知”,而广播就是所有线程都会得到“通知”。对于广播情况,所有被“通知”到的线程也要经过由互斥锁控制的独木桥。

#include   
#include   
#include   
#define BUFFER_SIZE 5  
pthread_mutex_t g_mutex;  
pthread_cond_t g_cond;  
typedef struct {  
    char buf[BUFFER_SIZE];  
    int count;  
} buffer_t;  
buffer_t g_share = {"", 0};  
char g_ch = 'A';  
void* producer( void *arg )  
{  
    printf( "Producer starting.\n" );  
    while( g_ch != 'Z' ) {  
        pthread_mutex_lock( &g_mutex );  
        if( g_share.count < BUFFER_SIZE ) {  
            g_share.buf[g_share.count++] = g_ch++;  
            printf( "Prodcuer got char[%c]\n", g_ch - 1 );  
            if( BUFFER_SIZE == g_share.count ) {  
                printf( "Producer signaling full.\n" );  
                pthread_cond_signal( &g_cond );  
            }  
        }  
        pthread_mutex_unlock( &g_mutex );  
    }  
    printf( "Producer exit.\n" );  
    return NULL;  
}  
void* consumer( void *arg )  
{  
    int i;  
    printf( "Consumer starting.\n" );  
    while( g_ch != 'Z' ) {  
        pthread_mutex_lock( &g_mutex );  
        printf( "Consumer waiting\n" );  
        pthread_cond_wait( &g_cond, &g_mutex );  
        printf( "Consumer writing buffer\n" );  
        for( i = 0; g_share.buf[i] && g_share.count; ++i ) {  
            putchar( g_share.buf[i] );  
            --g_share.count;  
        }  
        putchar('\n');  
        pthread_mutex_unlock( &g_mutex );  
    }  
    printf( "Consumer exit.\n" );  
    return NULL;  
}  
int main( int argc, char *argv[] )  
{  
    pthread_t ppth, cpth;  
    pthread_mutex_init( &g_mutex, NULL );  
    pthread_cond_init( &g_cond, NULL );  
    pthread_create( &cpth, NULL, consumer, NULL );  
    pthread_create( &ppth, NULL, producer, NULL );  
    pthread_join( ppth, NULL );  
    pthread_join( cpth, NULL );  
    pthread_mutex_destroy( &g_mutex );  
    pthread_cond_destroy( &g_cond );  
    return 0;  
}  

pthread_create( &cpth, NULL, consumer, NULL ); 和pthread_create( &ppth, NULL, producer, NULL ); 之间加入一个长的延时函数usleep(100),确保consumer线程先行执行到pthead_cond_wait() 处。不然会死锁

pthread_cond_wait: 对g_mutex 进行了解锁,这个时候producer 获取锁,制造数据然后释放锁,这个时候回到线程consumer pthread_cond_wait 加锁动作,如果接受条件允许通过,在consumer 线程后面对g_mutex 解锁

5.3 自旋锁

pthread_spin_t spin;
pthread_spin_init();
pthread_spin_lock();//得不到,忙等待busyloop,一直占用cpu;而互斥锁在得不到时会挂起等待,让出CPU
pthread_spin_unlock();
pthread_spin_destroy();

5.4 读写锁
读读共享,读写互斥,读优先级高
应用于大量读进程,少量写进程(读者写者模型)

pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock,NULL);
pthread_rwlock_rdlock(&rwlock);/pthread_rwlock_wrlock(&rwlock);
pthread_rwlock_unlock(rwlock);
pthread_rwlock_destroy(rwlock);
#include 
#include 
#include 
#include 
int count=0;
pthread_rwlock_t rwlock;
void* route_read(void *arg)
{
    int i=*(int *)arg;
    free(arg);
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("%d read %d\n",i,count);
        pthread_rwlock_unlock(&rwlock);
        usleep(1000);
    }
}
void* route_write(void *arg)
{
    int i=*(int *)arg;
    free(arg);
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        printf("%d write %d\n",i,++count);
        pthread_rwlock_unlock(&rwlock);
        usleep(1000);
    }
}

int main()
{
    pthread_t id[8];
    int i=0;
    pthread_rwlock_init(&rwlock,NULL);
    for(i=0;i<5;i++)
    {
        int *p=(int *)malloc(sizeof(int));
        *p=i;
        pthread_create(&id[i],NULL,route_read,(void *)p);
    }
    for(i=5;i<8;i++)
    {
        int *p=(int *)malloc(sizeof(int));
        *p=i;
        pthread_create(&id[i],NULL,route_write,(void *)p);
    }

    for(i=0;i<8;i++)
    {
        pthread_join(id[i],NULL);
    }
    pthread_rwlock_destroy(&rwlock);
}

其他:后面对线程的内存占用进行梳理

你可能感兴趣的:(pthread 介绍)