linux多线程pthread

来源:互联网 发布:个人卖数据 编辑:程序博客网 时间:2024/05/17 06:08

http://blog.chinaunix.net/uid-22163090-id-401346.html

1.     所谓线程就是“一个进程内部的一个控制序列”。也就是一个进程内部的并行的基础!


2.     Linux进程可以看成只有一个控制线程:
        一个进程在同一时刻只做一件事情。有了多个控制线程以后,
        在程序设计时可以把进程设计成在同一时刻能够做不止一件事,
        每个线程处理各只独立的任务。即所谓并行!
       
3.      线程的优点:
            (1) 通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。
            (2) 多个线程可以自动共享相同的存储地址空间和文件描述符。
            (3) 有些问题可以通过将其分解从而改善整个程序的吞吐量。
            (4) 交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中
                    处理用户输入输出的部分与其它部分分开。

4.        线程的缺点:
           线程也有不足之处。编写多线程程序需要更全面更深入的思考。
           在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的
           变量而造成不良影响的可能性是很大的。调试一个多线程程序也
           比调试一个单线程程序困难得多。       
      
5.     线程标识:
        我们已经知道进程有进程ID就是pid_t,那么线程也是有自己的ID的pthread_t数据类型!
        注意:实现的时候可以用一个结构来代表pthread_t数据类型,所以可以移植的操作系统
               不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。
       
        >>>>>:      
        关于比较线程ID函数:
        #include <pthread.h>
        int pthread_equal(pthread_t tid1,pthread_t tid2 );
        注意:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,
                所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,
                而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。
                所以不能直接使用==判读,而应该使用pthread_equal来判断。
        返回:非0值->相等;0->不等价    
              
        >>>>>:
        获取自身线程的id:
        #include <pthread.h>
        pthread_t pthread_self(void);              
        返回:调用线程的线程id      
        注意:在使用pthread_create(pthread_t *thread_id,NULL,void* (*fun) (void *),void * args);
                虽然第一个参数中已经保存了线程ID,但是,前提是主线程首先执行时,才能实现的,
                而如果不是,那么thread指向一个未初始化的变量。那么子线程想使用时,应该使用pthread_self();      
              
6.     线程的创建:
        #include <pthread.h>
        int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);          
       
        参数:
              第一个参数为指向线程标识符的指针。
              第二个参数用来设置线程属性。
              第三个参数是线程运行函数的起始地址。
              最后一个参数是运行函数的参数
       
        返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的
        线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如
        果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个
        结构的地址作为arg的参数传入。       
              
        注意:restrict修饰,
        pthread_join
        运行:
        在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库
        if不加上就会报错:undefined reference to `pthread_create'。
              
7.      线程的终止与等待:
        线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。
        <1>. 线程从启动例程中返回,返回值是线程的退出码。
        <2>. 线程可以被同一进程中的其他线程取消。
        <3>. 线程退出调用pthread_exit.
        #include <pthread.h>
        void pthread_exit( void * rval_ptr );       
       
        rval_ptr 是一个无类型指针,与传给启动例程的单个参数类似。
        进程中的其他线程可以调用pthread_join函数访问到这个指针。
       
        pthread_join:获得进程的终止状态。
       
        int pthread_join( pthread_t thread, void ** rval_ptr );
        若成功返回0,否则返回错误编号。
        调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。
        如果线程只是从它的启动历程返回,rval_ptr将包含返回码。
       
8.     pthread_detach:使线程进入分离状态
       
        int pthread_detach( pthread_t tid );
        若成功则返回0,否则返回错误编号。
        注意:默认情况下是pthread_join等待线程的结束,并可以获得结束时的状态!
                如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。
                当线程被分离时,并不能用pthread_join函数等待它的终止状态。
       
9.     pthread_cancel:取消同一进程中的其他线程(注意是取消其他线程)
       
        int pthread_cancel(pthread_t tid);
        若成功返回0,否则返回错误编号。
        注意:pthread_cancel并不等待线程终止,它仅仅提出请求。是否真的执行还看目标线程的
                state和type的设置了!
       
10.  pthread_cleanup_push 和 pthread_cleanup_pop:线程清理处理程序       
       
        void pthread_cleanup_push( void ( * rtn ) ( void * ),void *arg );
        void pthread_cleanup_pop( int exe );
        参数:
                rtn:  处理程序入口地址
                arg: 传递给处理函数的参数
        线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序,线程可以建立多个清理处理程序。
        注意:此处是使用栈保存的,所以是先进后处理原则!
                如果线程是通过从启动例程中返回而终止的,它的处理程序就不会调用。
       
       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        ATTENTION:
                        pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop
                        之前异常退出(return是正常退出,其他是异常),那么系统就会执行这个回调函数(回调函
                        数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,
                        pthread_cleanup_pop就把对应的回调函数取消了,
       
        所以请注意:
                只有在“清理函数”的设置和取消之间有异常的退出,才会调用我们设置的“清理函数”。
                否则是不会输出的!
                一般的触发条件是:在之间有pthread_exit(非正常退出);或者有取消点时候!
               
        关于“取消点” ( cancellation point ):
                例如执行下面代码:
                                    printf(" sleep\n");
                                    sleep(10);
                                    printf(" wake \n");
        在sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。
        >>>>>:       
                函数:pthread_testcancel():
                描述:函数在运行的线程中创建一个取消点,如果cancellation无效则此函数不起作用。
               
                pthread的建议是:如果一个函数是阻塞的,那么你必须在这个函数前后建立 “ 取消点 ”, 比如:
                                    printf(" sleep\n");
                                    pthread_testcancel();
                                    sleep(10);
                                    pthread_testcancel();
                                    printf(" wake \n");
                在执行到pthread_testcancel的位置时,线程才可能响应cancel退出进程。
               
                对于一些函数来说本身就是有cancellation point 的,那么可以不管,但是大部分还是没有的,
                所以要使用pthread_testcancel来设置一个取消点,那么也并不是对于所有的函数都是有效的,
                对于有延时的函数才是有效的,更清楚的说是有时间让pthread_cancel响应才是OK的!
               
        附加:
                POSIX中的函数cancellation点的:
                pthread_join
                pthread_cond_wait
                thread_cond_timewait
                pthread_testcancel
                sem_wait
                sigwait         都是cancellation点.
                下面的这些系统函数也是cancellation点:
                accept
                fcntl
                open
                read
                write
                lseek
                close
                send
                sendmsg
                sendto
                connect
                recv
                recvfrom
                recvmsg
                system
                tcdrain
                fsync
                msync
                pause
                wait
                waitpid
                nanosleep
 
                其它的一些函数如果调用了上面的函数, 那么, 它们也是cancellation点.
                int pthread_setcancelstate (int STATE, int *OLDSTATE);
                用于允许或禁止处理cancellation,
                STATE可以是:PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DISABLE

                int pthread_setcanceltype (int TYPE, int *OLDTYPE);
                设置如何处理cancellation, 异步的还是推迟的.
                TYPE可以是:PTHREAD_CANCEL_ASYNCHRONOUS, PTHREAD_CANCEL_DEFERRED
                   
        >>>>           
        摘录:http://blogt.chinaunix.net/space.php?uid=23381466&do=blog&id=58787

                什么是取消点(cancelation point)?

                资料中说,根据POSIX标准,pthread_join()、pthread_testcancel()、 pthread_cond_wait()、
                pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及 read()、write()等会引起阻塞
                的系统调用都是Cancelation-point。而其他pthread函数都不会引起 Cancelation动作。但
                是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函
                数都不是 Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置
                EINTR错误码,因此可以在需要作为 Cancelation-point的系统调用前后调用pthread_testcancel(),
                从而达到POSIX标准所要求的目标,即如下代码段:

                pthread_testcancel();
                retcode = read(fd, buffer, length);
                pthread_testcancel();

                我发现,对于C库函数来说,几乎可以使线程挂起的函数都会响应CANCEL信号,终止线程,
                包括sleep、delay等延时函数。       
                   
       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
       
11.       
        #include <pthread.h>
        int pthread_setcancelstate( int state, int* oldstate );

        描述:
        pthread_setcancelstate() f函数设置线程取消状态为state,并且返回前一个取消点状态oldstate。
        取消点有如下状态值:
        PTHREAD_CANCEL_DISABLE:取消请求保持等待,默认值。
        PTHREAD_CANCEL_ENABLE:取消请求依据取消类型执行;参考pthread_setcanceltype()。
       
        参数:
        state: 新取消状态。
        oldstate: 指向本函数所存储的原取消状态的指针。

        ///////////////////////////////////////////////////////////////////////////   
   
        #include <pthread.h>
        int pthread_setcanceltype( int type, int* oldtype );

        描述:
        pthread_setcanceltype()函数设置运行线程的取消类型为type,并且返回原取消类型与oldtype。
       
        取消类型值:
    PTHREAD_CANCEL_ASYNCHRONOUS:如果取消有效,新的或者是等待的取消请求会立即执行。
    PTHREAD_CANCEL_DEFERRED:如果取消有效,在遇到下一个取消点之前,取消请求会保持等待,默认值。
   注意if没有取消点,那么陷入死锁!!!
        注意:标注POSIX和C库的调用不是异步取消安全地。

        参数:
        type: 新取消类型
        oldtype: 指向该函数所存储的原取消类型的指针。

   
12.   posix变量一次初始化问题:
        >>>>>:
        如果我们需要对一个posix变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始话进行控制。
        但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。
       
        pthread_once_t  once_control = PTHREAD_ONCE_INIT;
        int pthread_once( pthread_once_t *once_control, void( * init_routine ) ( void ) );
        参数:once_control         控制变量
                init_routine              初始化函数
        若成功返回0,若失败返回错误编号。
        pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。
       
        pthread_once 工作原理:
                                    pthread_once函数首先检查控制变量,判断是否已经完成初始化,如果完成
                                    就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。
                                    如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到
                                    那个现成完成初始话返回。
        
13.    线程的私有数据:
        在进程内的所有线程共享相同的地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量,
        都可以被进程所有的线程读写。那怎样才能使线程序拥有自己的私有数据呢。posix提供了一种方法,
        创建 " 线程键 "。
        #include <pthread.h>
        int pthread_key_create(pthread_key *key,void(*destructor)(void *));
        参数:
        key             私有数据键
        destructor    清理函数参数
        if destructor 不为空,那么系统将调用这个函数来释放绑定在这个键上的内存块。
       
        这个函数常和函数pthread_once一起使用,为了让这个键只被创建一次。
       

14.    关于: " 线程存储 "    


1.基础线程创建:

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

void * print_id( void * arg )        //!> 这是线程的入口函数                               
{
    printf("The Current process is: %d \n", getpid());                                //!> 当前进程ID   
    printf( "The Current thread id : %d \n", (unsigned)pthread_self() );    //!> 注意此处输出的子线程的ID
}

int main( )
{
    pthread_t        t;
    int                 t_id;
   
    t_id = pthread_create( &t, NULL, print_id, NULL );        //!> 简单的创建线程
   
    if( t_id != 0 )                        //!> 注意创建成功返回0                               
    {
        printf("\nCreate thread error...\n");
        exit( EXIT_FAILURE );
    } 
    sleep( 1 );
    printf("\nThe Current process is: %d \n", getpid());                         //!> 当前进程ID       
    printf( "The Main thread id : %d \n", (unsigned)pthread_self() );    //!> 注意输出的MAIN线程的ID
    return 0;
}



2.测试线程的创建和退出

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

void * entrance_1( void * arg )                //!> 第一个创建的线程的入口函数
{
    printf( " thread 1 id == %d , run now ... \n", ( unsigned )pthread_self() );
    sleep( 3 );
    return ( ( void * ) 1 );
}

void * entrance_2( void * arg )                //!> 第二个创建的线程的入口函数
{
    printf( " thread 2 id == %d , run now ... \n", ( unsigned )pthread_self() );
    sleep( 3 );
    return ( ( void * ) 2 );
}

int main( )
{
    pthread_t        t1 = -1;    //!> 最好是初始化:因为下面的pthread_join是需要判断是否成功在输出的
    pthread_t        t2 = -1;
    int              tid1;
    int              tid2;
    void     *        ret;
   
    tid1 = pthread_create( &t1, NULL, entrance_1, NULL );    //!> 简单的创建线程
    tid2 = pthread_create( &t2, NULL, entrance_2, NULL );
   
    if( tid1 != 0 || tid2 != 0 )    //!> 创建线程失败                   
    {
        printf( "Create thread error...\n" );
        exit( EXIT_FAILURE );
    }
   
    if( t1 != -1 )                //!> 也就是线程还没有结束
    {
        if ( pthread_join( t1, &ret ) == 0 )    //!> join success
        {
            printf( " thread 1 get the return of pthread_join == %d \n", 2 );/ )
 {
     pthread_mutex_init( &mutex, NULL );        //!> 初始化为默认的互斥锁
    
     printf("主函数:创建2个子线程...\n");
    
     create_two_thread();        //!> 创建2个线程
    
     printf("主函数:等待线程完成任务...\n");
    
     wait_two_thread();        //!> 等待线程完成任务
                                 //!> 线程任务完成才可以执行下面代码
     printf("线程任务完成...\n");
    
     printf("Num == %d \n\n", num);
    
     return 0;
 }
 
4.双线程处理:冒泡排序算法

//        双线程处理冒泡排序(多线程也一样)
//        实现从“小”--->“大”排序

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

int g_arr[] = { 10, 23, 12, 34, 5, 29, 90, 9, 78, 44 };        //!> 全局的要排序的数组
pthread_t                thread[2];               //!> 两个线程
pthread_mutex_t        mutex;                //!> 互斥锁

int                         g_i = 0;                     //!> 全局的剩余排列次数

//!> 打印数组
void print_array()
{
    int i;
    for( i = 0; i < 10; i++ )
    {
        printf( " %d ", g_arr[i] );
    }
    printf("\n");
}

//!> 交换元素
void swap_elem( int * a, int * b )
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

//!> 线程1入口函数
void * entrance_1( void * arg )
{
    int j;
    for( g_i = 0; g_i < 10; g_i++ )    //!> 外层循环
    {
        pthread_mutex_lock( &mutex );        //!> 加锁
       
        printf( "线程1后台执行排序...\n" );
       
        for( j = 0; j < ( 10 - g_i - 1 ); j++ )    //!> 内层循环
        {
            if( g_arr[j] > g_arr[j+1] )
            {
                swap_elem( &g_arr[j], &g_arr[j+1] );
            }
        }
       
        pthread_mutex_unlock( &mutex );    //!> 解锁
       
        sleep( 1 );
    }
}

//!> 线程2入口函数
void * entrance_2( void * arg )
{
    int j;
    for( g_i = 0; g_i < 10; g_i++ )    //!> 外层循环
    {
        pthread_mutex_lock( &mutex );        //!> 加锁
       
        printf( "线程2后台执行排序...\n" );
           
        for( j = 0; j < ( 10 - g_i - 1 ); j++ )    //!> 内层循环
        {
            if( g_arr[j] > g_arr[j+1] )
            {
                swap_elem( &g_arr[j], &g_arr[j+1] );
            }
        }
   
        pthread_mutex_unlock( &mutex );    //!> 解锁

        sleep( 2 );   
    }
}

//!> 创建2个线程
void create_two_thread()
{
    memset( &thread, 0, sizeof( thread ) );            //!> 初始化为0(作为下面的判断进程是否创建OK依据)
   
    if( ( pthread_create( &thread[0], NULL, entrance_1, NULL ) ) == 0 )
    {
        printf("线程1创建OK ...\n");
    }
    else
    {
        printf("线程1创建Error ...\n");
        exit( EXIT_FAILURE );
    }
   
    if( ( pthread_create( &thread[1], NULL, entrance_2, NULL ) ) == 0 )
    {
        printf("线程2创建OK ...\n");
    }
    else
    {
        printf("线程2创建Error ...\n");
        exit( EXIT_FAILURE );
    }
   
}

//!> 线程执行与等待
void do_and_wait()
{
    if( thread[0] != 0 )//!> 由于在create_two_thread中初始化=0,if床架ok,那么不可能还是0
    {
        pthread_join( thread[0], NULL );    //!> 等待线程1结束,不结束不执行下面代码
        printf("线程1执行结束退出...\n");
    }
    else
    {
        printf("线程1创建Error...\n");
        exit( EXIT_FAILURE );
    }
   
    if( thread[1] != 0 )
    {
        pthread_join( thread[1], NULL );    //!> 等待线程1结束,不结束不执行下面代码
        printf("线程2执行结束退出...\n");
    }
    else
    {
        printf("线程2创建Error...\n");
        exit( EXIT_FAILURE );
    }
   
}

int main( )
{
    printf("主函数:下面创建2个线程共同处理冒泡排序...\n");
   
    pthread_mutex_init( &mutex, NULL );
   
    print_array();                //!> 打印排序前的结果
   
    create_two_thread();        //!> 创建线程
    do_and_wait();            //!> 执行线程and等待
   
    printf( "排序完成:\n" );

    print_array();                //!> 打印排序后的结果
       
    return 0;
}

5.线程清理处理程序

//        线程清理处理程序TEST

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

void clean(void *arg)
{
    printf("清理: %s \n", (char *)arg);
}

void * entrance( void * arg )
{
    printf("线程开始...\n");
   
    pthread_cleanup_push( clean, "线程处理程序1" );
    pthread_cleanup_push( clean, "线程处理程序2" );
   
    printf("pthread clean 完成...\n");
   
    sleep(3);
   
    pthread_exit((void *)0);        //!> 我们知道:清理函数只有在异常退出时候才会做一些清理工作
                                                //!> 所以此处的退出是异常退出来测试的!
   
    pthread_cleanup_pop(0);    
    pthread_cleanup_pop(0);    
   
   
   
}

int main( )
{
    pthread_t     tid;
    void     *     ret = NULL;
   
    if( ( pthread_create( &tid, NULL, entrance, (void *)1 ) ) != 0 )
    {
        printf("创建线程失败...\n");
        exit( EXIT_FAILURE );
    }
   
    pthread_join( tid, &ret );       
   
    if( ret )                            //!> 注意此处相当于是抛出异常
    {                                //!> 避免子线程的异常退出造成的空指针情况
        printf( "结束:code == %d\n", *( ( int * ) ret) );
    }
   
    return 0;
}

/////////////////////////////////////////////////////////////
//    DEMO——2

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

void clean( void * arg )
{
    printf("清理函数执行...\n");
}

void * entrance( void * arg )
{
    int old_type, old_state;
    int i = 0;
   
    pthread_cleanup_push( clean, NULL );    //!> 设置清理函数
    printf("下面设置对本线程的“取消”无效\n");
    pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &old_state );
                                                //!> 设置对本线程的“取消”无效
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&old_type);    //>>>>>>>>>>> 目标句1
   
    while( 1 )
    {
        ++i;
        printf("子线程runing...\n");
        sleep( 2 );
        if( 5 == i )
        {
            printf("下面取消设置对本线程的“取消”无效\n");
            pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
        }
    }
   
    pthread_cleanup_pop( 0 );
}

int main( int argc, char ** argv )
{
    pthread_t    tid;
    int               res;
    void *          ret;
   
    pthread_create( &tid, NULL, entrance, NULL );
    sleep( 2 );
    printf("请求子线程退出...\n");
    pthread_cancel( tid );            //!> 请求子线程退出
   
    res = pthread_join( tid, &ret );    //!> 等待子线程退出
   
    if( ret != PTHREAD_CANCELED )    //!> 非安全退出
    {
        printf("pthread_join 失败...\n");
        exit( EXIT_FAILURE );
    }
    else
    {
        printf("Success..");
    }
       
    exit( EXIT_SUCCESS );   
}


    分析:
        没有加上“目标句”的结果是:
                                        下面设置对本线程的“取消”无效
                                        子线程runing...
                                        请求子线程退出...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        下面取消设置对本线程的“取消”无效
                                        子线程runing...                        //!> 比下面多的
                                        清理函数执行...
                                        Success..                                //!> 与下面不一样的
                                       
      加上后:
                                        下面设置对本线程的“取消”无效
                                        子线程runing...
                                        请求子线程退出...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        下面取消设置对本线程的“取消”无效
                                        清理函数执行...
                                        pthread_join 失败...                    //!> 与上面不一样的
                                       
       这句的作用是将取消类型设置为PTHREAD_CANCEL_ASYNCHRONOUS,即取消请求会被立即响应                                   
       那么就不会再次进入等待下一个“取消点”再进行取消!!!
   
       注意:if在while中没有sleep,那么程序会无限run,我们知道sleep是相当于是释放一下线程,那么此时的主线程中的cancel信号又被接收到,那么if本函数可以响应了,那么就cancle了,if将sleep去掉,那么死循环!再次解释:所谓pthread_cancel仅仅是请求某个线程退出,那么究竟是不是退出还要看state和type的设置!                   



6. pthread_once 工作原理code


//        pthread_once 函数使用

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

pthread_once_t        once = PTHREAD_ONCE_INIT;    //!> once宏赋值

//!> 初始化执行函数
void once_init( void )
{
    printf("初始化成功! 我的ID == %d\n", (unsigned)pthread_self());
}

//!> 线程入口函数
void * entrance( void * arg )
  
    printf("子线程:ID == %d \n", (unsigned)pthread_self());
    //!> once = PTHREAD_ONCE_INIT;        //!> 测试使用(下面的要求)
    pthread_once( &once, once_init );        //!> 此处也有初始化
}

//!> main函数
int main( int argc, char * argv[] )
{
    pthread_t        pid;
   
    pthread_create( &pid, NULL, entrance, NULL );

    printf("主函数ID == %d \n", (unsigned)pthread_self());
   
//!>    pthread_join( pid, NULL );            //!> 分析点
   
    pthread_once( &once, once_init );    //!> 调用一次初始化函数
   
    pthread_join( pid, NULL );
   
    return 0;
}

        if pthread_join是在主函数初始化后面,那么就是主函数初始化的
        结果是: 主函数ID == 441960192
                       初始化成功! 我的ID == 441960192
                      子线程:ID == 433944320
        显而易见是主函数初始化的!
       
        if pthread_join是在之前,那么就是要等待子函数执行ok后才执行自己的下面代码
        但是此时已经初始化ok了,所以不在初始化!
        结果是:主函数ID == 210818816
                      子线程:ID == 202802944
                      初始化成功! 我的ID == 202802944
        显然是子函数执行的初始化!

        本质:   其实就是操作once变量而已,与互斥变量的本质是一样的!!!
                      我们可以这样测试在entrance中加入once = PTHREAD_ONCE_INIT;
        结果是:主函数ID == 1590228736
                      初始化成功! 我的ID == 1590228736
                      子线程:ID == 1582212864
                      初始化成功! 我的ID == 1582212864
       
        感兴趣的可以使用pthread_mutex_t 的互斥变量处理,效果一样!
        还有最最简单的就是bool值处理!此处不建议!

7.pthread_key_create线程键 与 线程存储

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

pthread_once_t        once = PTHREAD_ONCE_INIT;
pthread_key_t        key;            //!> 键值
   
int                         g_val = 10;    //!> 传说中的独享值,呵呵

void once_init_key()
  
    if( pthread_key_create( &key, NULL ) == 0 )    //!> 创建线程键值
    {                                                    //!>
        printf("创建线程键OK ...\n");
    }
}

void * entrance( void * arg )
{
    int * val;
    printf("子线程:ID == %d \n", (unsigned)pthread_self());
   
    pthread_setspecific( key, &g_val );               //!> 将 g_val 作为一个每个进程的独享值
   
    val = ( int * )pthread_getspecific( key );        //!> 取出那个值
                                                        //!> 此后对于这些量都有自己的处理方式,
                                                        //!> 名称相同但是内容不同!!!   
                                                        //!> 对于文件的处理是最好的!!!
    printf("ID == %d, Value == %d\n",  (unsigned)pthread_self(), *val);
}


int main( )
{
    pthread_t        tid, tid2;
    void     *      ret1;
    void     *     ret2;
   
    if( pthread_create( &tid, NULL, entrance, NULL ) != 0 )            //!> 线程1
    {
        printf("创建线程1失败...\n");
        exit( EXIT_FAILURE );
    }
   
    if( pthread_create( &tid2, NULL, entrance, NULL ) != 0 )            //!> 线程2
    {
        printf("创建线程2失败...\n");
        exit( EXIT_FAILURE );
    }
           
    printf("主函数:ID == %d \n", (unsigned)pthread_self());
       
    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   
   
    pthread_once( &once, once_init_key );    //!> 创建一个键值
   
    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
   
    printf("下面等待子线程执行ok... \n");
   
    pthread_join( tid, &ret1 );                    //!> 等待线程( 必不可少 )
    pthread_join( tid2, &ret2 );
   
    return 0;
}

结果:
    主函数:ID == 1588877056
    创建线程键OK ...
    子线程:ID == 1580861184
    ID == 1580861184, Value == 10
    下面等待子线程执行ok...
    子线程:ID == 1572468480
    ID == 1572468480, Value == 10


1.    线程属性:
            
 使用pthread_attr_t类型表示,我们需要对此结构体进行初始化,
                 初始化后使用,使用后还要进行去除初始化!
                 pthread_attr_init:初始化
                 pthread_attr_destory:去除初始化       
               
                #include <pthread.h>
                int pthread_attr_init(pthread_attr_t *attr);
                int pthread_attr_destroy(pthread_attr_t *attr);   
                若成功返回0,若失败返回-1。
               
                pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现
                支持的线程所有属性的默认值。
               
                如果pthread_attr_init实现时为属性对象分配了动态内存空间,
                pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经
                pthread_attr_destroy去除初始化之后的pthread_attr_t结构被
                pthread_create函数调用,将会导致其返回错误。
               
                线程属性结构如下:

                typedef struct
                {
                       int                           detachstate;     线程的分离状态
                       int                          schedpolicy;   线程调度策略
                       struct sched_param      schedparam;   线程的调度参数
                       int                          inheritsched;    线程的继承性
                       int                          scope;          线程的作用域
                       size_t                      guardsize; 线程栈末尾的警戒缓冲区大小
                       int                          stackaddr_set;
                       void *                     stackaddr;      线程栈的位置
                       size_t                      stacksize;       线程栈的大小
                }pthread_attr_t;
               
                下面主要讨论此结构体!!!
               
2.    分离状态:
            线程的分离状态决定一个线程以什么样的方式来终止自己。
           
            我们已经在前面已经知道,在默认情况下线程是非分离状态的,这种情况   
            下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回       
            时,创建的线程才算终止,才能释放自己占用的系统资源。   
           
            分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,
            马上释放系统资源。
           
            通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,
            主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是if有一
            些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我
            们就无须等待管理,只要线程自己结束了,自己释放src就可以咯!这样更
            方便!
           
            #include <pthread.h>
            int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
            int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);
            参数:attr:线程属性变量
                    detachstate:分离状态属性   
            若成功返回0,若失败返回-1。
           
            设置的时候可以有两种选择:
            <1>.detachstate参数为:PTHREAD_CREATE_DETACHED     分离状态启动
            <2>.detachstate参数为:PTHREAD_CREATE_JOINABLE    正常启动线程
           
3.    线程的继承性:
           
            函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设
            置和得到线程的继承性!
           
            #include <pthread.h>
            int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
            int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
            参数:
            attr                线程属性变量
            inheritsched     线程的继承性
            若成功返回0,若失败返回-1。
           
            请注意:
            继承性决定调度的参数是从创建的进程中继承还是使用在 
            schedpolicy和schedparam属性中显式设置的调度信息。           
                                   
            线程没有默认的继承值设置,所以如果关心线程的调度策略和参数,
            只能手动设置!
           
            可设置参数:
            PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!
            PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于
                                                schedpolicy和schedparam属性中显式
                                                设置的调度信息!
                                               
>>>>>:    下面补充线程调度策略和调度参数:
            <1>.调度策略:
           
                    函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用
                    来设置和得到线程的调度策略。
                   
                    int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
                    int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
                    参数:
                            attr            线程属性变量
                            policy        调度策略   
                    若成功返回0,若失败返回-1。
                   
                    所谓调度策略也就是我们之前在OS中所学过的那些调度算法:
                    SCHED_FIFO    :先进先出
                    SCHED_RR       :轮转法
                    SCHED_OTHER    :其他方法
                   
                    SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR
                    支持优先级的使用,他们分别为1和99,数值越大优先级越高.
                   
                    注意:
                            > 此处的SCHED_FIFO是允许被高优先级抢占的!
                            > 也就是有高优先级的必须先运行
                            > SCHED_RR是设置一个时间片
                            > 当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量
                            上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤
                            醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先
                            织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量
                            被解锁时,高优先级线程将总是被首先解除阻塞。
                           
            <2>.调度参数:
                   
                    函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别
                    用来设置和得到线程的调度参数。
                   
                       

                    int pthread_attr_getschedparam(const pthread_attr_t *,struct
                    sched_param *);
                    int pthread_attr_setschedparam(pthread_attr_t *,const struct
                    sched_param *);
                    参数:
                            attr            线程变量属性
                            param        sched_parm 结构体
                    若成功返回0,若失败返回-1。
                   
                    /usr/include /bits/sched.h
                    struct sched_param
                    {
                           int sched_priority;    //!> 参数的本质就是优先级
                    };
                    注意:大的权值对应高的优先级!
                    系统支持的最大和最小的优先级值可以用函数:
                    sched_get_priority_max和sched_get_priority_min得到!
                   
                    #include <pthread.h>
                    int sched_get_priority_max( int policy );
                    int sched_get_priority_min( int policy );
                    参数:max_:    系统支持的优先级的最小值
                            min_ :    系统支持的优先级的最大值
                   
                    使用:max_ = sched_get_priority_max( policy );
                            min_ = sched_get_priority_min( policy );
                            注意参数是policy调用策略,也就是说对于不同的策略的值是不
                            一样的!
               
                    附录:来自
                    http://www.yuanma.org/data/2006/0823/article_1392.htm
                    policy = SCHED_OTHER
                    max_priority = 0
                    min_priority = 0
   
                    Show SCHED_FIFO of priority
                    max_priority = 99
                    min_priority = 1
                   
                    Show SCHED_RR of priority
                    max_priority = 99
                    min_priority = 1
   
                    Show priority of current thread
                    priority = 0
                   
3.    线程的作用域:
                               
            函数pthread_attr_setscope和pthread_attr_getscope分别
            用来设置和得到线程的作用域。       
            #include <pthread.h>   
            int    pthread_attr_getscope( const pthread_attr_t * attr, int * scope );
            int pthread_attr_setscope( pthread_attr_t*, int scope );
            参数:
                    attr               线程属性变量
                    scope         线程的作用域       
            若成功返回0,若失败返回-1。
           
            作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是
            PTHREAD_SCOPE_PROCESS(进程内竞争资源)
            PTHREAD_SCOPE_SYSTEM   (系统级竞争资源)。
                   
4.    线程堆栈的大小
           
            函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得
            到线程堆栈的位置。
           
            int pthread_attr_getstacksize(const pthread_attr_t *,size_t * stacksize);
            int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);
            参数:attr                线程属性变量
                    stacksize        堆栈大小
            若成功返回0,若失败返回-1。           
       
5.    线程堆栈的地址           
           
            #include <pthread.h>
            int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);
            int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);
            参数:attr               线程属性变量
                    stackaddr     堆栈地址           
            若成功返回0,若失败返回-1。
           
            注意:pthread_attr_getstackaddr已经过期,现在使用的是:pthread_attr_getstack

6.    警戒缓冲区
           
            函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得
            到线程栈末尾的警戒缓冲区大小。

            #include <pthread.h>                   
            int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict
            guardsize);
            int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);
            若成功返回0,若失败返回-1。
           
            值得注意:
                        线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存
                        大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线
                        程属性设为0,从而不允许属性的这种特征行为发生:在这种情况
                        下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了
                        修改,系统就会认为我们会自己管理栈,并使警戒栈缓冲区机制无
                        效,等同于把guardsize线程属性设为0。


linux thread与fork的对比

 进程原语线程原语 描述  forkpthread_create  创建新的控制流 exitpthread_exit 从现有的控制流退出 waitpidpthread_join 从控制流中得到退出状态 atexitpthread_clean_push 注册在退出控制流时执行的函数 getpidpthread_self 获得控制流ID abortpthread_cancel 请求控制流的非正常退出
0 0
原创粉丝点击