zoukankan      html  css  js  c++  java
  • Linux中线程使用详解

    线程与进程
    为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。

      使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

      使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

      除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

      1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

      2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

    3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

     

    一、线程标识

    • 线程有ID, 但不是系统唯一, 而是进程环境中唯一有效.
    • 线程的句柄是pthread_t类型, 该类型不能作为整数处理, 而是一个结构.

    下面介绍两个函数:

    • 头文件: <pthread.h>
    • 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
    • 返回值: 相等返回非0, 不相等返回0.
    • 说明: 比较两个线程ID是否相等.

     

    • 头文件: <pthread.h>
    • 原型: pthread_t pthread_self();
    • 返回值: 返回调用线程的线程ID.

    二、线程创建

     在执行中创建一个线程, 可以为该线程分配它需要做的工作(线程执行函数), 该线程共享进程的资源. 创建线程的函数pthread_create()

    • 头文件: <pthread.h>
    • 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
    • 返回值: 成功则返回0, 否则返回错误编号.
    • 参数:
      • tidp: 指向新创建线程ID的变量, 作为函数的输出.
      • attr: 用于定制各种不同的线程属性, NULL为默认属性(见下).
      • start_rtn: 函数指针, 为线程开始执行的函数名.该函数可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由 pthread_join()获取
      • arg: 函数的唯一无类型(void)指针参数, 如要传多个参数, 可以用结构封装.

    linux下多线程程序的编译方法:

           因为pthread的库不是linux系统的库,所以在进行编译的时候要加上     -lpthread

           # gcc filename -lpthread  //默认情况下gcc使用c库,要使用额外的库要这样选择使用的库

    例1:thread_create.c

     1 #include <stdio.h>
     2 #include <pthread.h>  //包线程要包含
     3 void *mythread1(void)
     4 {
     5    int i;
     6    for(i=0;i<100;i++)
     7    {
     8       printf("this is the 1st pthread,created by zieckey.
    ");
     9       sleep(1);
    10    }
    11 } 
    12 void *mythread2(void)
    13 {
    14     int i;
    15 for(i=0;i<100;i++)
    16    {
    17       printf("this is the 2st pthread,created by zieckey.
    ");
    18       sleep(1);
    19    }
    20 }
    21 int main()
    22 {
    23     int ret=0;
    24     pthread_tid1,id2;
    25    ret=pthread_create(&id1,NULL,(void*)mythread1,NULL);
    26     if(ret)
    27     {
    28         printf("create pthread error!
    ");
    29          return -1; 
    30     }
    31    ret=pthread_create(&id2,NULL,(void*)mythread2,NULL);
    32     if(ret)
    33     {
    34         printf("create pthread error!
    ");
    35          return  -1; 
    36     }
    37    pthread_join(id1,NULL);
    38    pthread_join(id2,NULL);
    39  
    40     return 0;
    41 }
    42 编译步骤:gcc thread_create .c -lpthread -othread_create

    例2: thread_int.c  //向线程函数传递整形参数

     1 #include <stdio.h>
     2 #include <pthread.h>
     3 #include <unistd.h>
     4 void *create(void *arg)
     5 {
     6     int *num;
     7     num=(int *)arg;
     8    printf("create parameter is %d 
    ",*num);
     9     return (void *)0;
    10 }
    11 int main(int argc,char *argv[])
    12 {
    13    pthread_t tidp;
    14     int error;
    15     int test=4;
    16     int*attr=&test;
    17  
    18   error=pthread_create(&tidp,NULL,create,(void*)attr);
    19     if(error)
    20      {
    21        printf("pthread_create is created is not created...
    ");
    22        return -1;
    23     }
    24    sleep(1);
    25   printf("pthread_create is created...
    ");
    26    return 0;
    27 }
    28 注:字符串,结构参数,一样道理

    三 线程的合并与分离

    我们首先要明确的一个问题就是什么是线程的合并。从前面的叙述中读者们已经了解到了,pthread_create()接口负责创建了一个线程。那么线程也属于系统的资源,这跟内存没什么两样,而且线程本身也要占据一定的内存空间。众所周知的一个问题就是C或C++编程中如果要通过malloc()或new分配了一块内存,就必须使用free()或delete来回收这块内存,否则就会产生著名的内存泄漏问题。既然线程和内存没什么两样,那么有创建就必须得有回收,否则就会产生另外一个著名的资源泄漏问题,这同样也是一个严重的问题。那么线程的合并就是回收线程资源了。
    线程的合并是一种主动回收线程资源的方案。当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。
    与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。
    线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。
     

    4. 线程的属性

     
    前面还说到过线程创建的时候是有属性的,这个属性由一个线程属性对象来描述。线程属性对象由pthread_attr_init()接口初始化,并由pthread_attr_destory()来销毁,它们的完整定义是:
    [cpp] view plain copy
     
    1. int pthread_attr_init(pthread_attr_t *attr);  
    2. int pthread_attr_destory(pthread_attr_t *attr);  
    那么线程拥有哪些属性呢?一般地,Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性。下面我们就分别来介绍这些属性。

    4.1 绑定属性

     
    说到这个绑定属性,就不得不提起另外一个概念:轻进程(Light Weight Process,简称LWP)。轻进程和Linux系统的内核线程拥有相同的概念,属于内核的调度实体。一个轻进程可以控制一个或多个线程。默认情况下,对于一个拥有n个线程的程序,启动多少轻进程,由哪些轻进程来控制哪些线程由操作系统来控制,这种状态被称为非绑定的。那么绑定的含义就很好理解了,只要指定了某个线程“绑”在某个轻进程上,就可以称之为绑定的了。被绑定的线程具有较高的相应速度,因为操作系统的调度主体是轻进程,绑定线程可以保证在需要的时候它总有一个轻进程可用。绑定属性就是干这个用的。
    设置绑定属性的接口是pthread_attr_setscope(),它的完整定义是:
    int pthread_attr_setscope(pthread_attr_t *attr, int scope);
    它有两个参数,第一个就是线程属性对象的指针,第二个就是绑定类型,拥有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。代码2演示了这个属性的使用。
    [cpp] view plain copy
     
    1. #include <stdio.h>  
    2. #include <pthread.h>  
    3. ……  
    4. int main( int argc, char *argv[] )  
    5. {  
    6.     pthread_attr_t attr;  
    7.     pthread_t th;  
    8.     ……  
    9.     pthread_attr_init( &attr );  
    10.     pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
    11.     pthread_create( &th, &attr, thread, NULL );  
    12.     ……  
    13. }  
    代码2设置线程绑定属性
    不知道你是否在这里发现了本文的矛盾之处。就是这个绑定属性跟我们之前说的NPTL有矛盾之处。在介绍NPTL的时候就说过业界有一种m:n的线程方案,就跟这个绑定属性有关。但是笔者还说过NPTL因为Linux的“蠢”没有采取这种方案,而是采用了“1:1”的方案。这也就是说,Linux的线程永远都是绑定。对,Linux的线程永远都是绑定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,而且会返回ENOTSUP错误。
    既然Linux并不支持线程的非绑定,为什么还要提供这个接口呢?答案就是兼容!因为Linux的NTPL是号称POSIX标准兼容的,而绑定属性正是POSIX标准所要求的,所以提供了这个接口。如果读者们只是在Linux下编写多线程程序,可以完全忽略这个属性。如果哪天你遇到了支持这种特性的系统,别忘了我曾经跟你说起过这玩意儿:)

    4.2 分离属性

     
    前面说过线程能够被合并和分离,分离属性就是让线程在创建之前就决定它应该是分离的。如果设置了这个属性,就没有必要调用pthread_join()或pthread_detach()来回收线程资源了。
    设置分离属性的接口是pthread_attr_setdetachstate(),它的完整定义是:
     
    [cpp] view plain copy
     
    1. pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);  
    它的第二个参数有两个取值:PTHREAD_CREATE_DETACHED(分离的)和PTHREAD_CREATE_JOINABLE(可合并的,也是默认属性)。代码3演示了这个属性的使用。
    [cpp] view plain copy
     
    1. #include <stdio.h>  
    2. #include <pthread.h>  
    3. ……  
    4. int main( int argc, char *argv[] )  
    5. {  
    6.     pthread_attr_t attr;  
    7.     pthread_t th;  
    8.     ……  
    9.     pthread_attr_init( &attr );  
    10.     pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
    11.     pthread_create( &th, &attr, thread, NULL );  
    12.     ……  
    13. }  
     
    代码3设置线程分离属性
     

    4.3 调度属性

     
    线程的调度属性有三个,分别是:算法、优先级和继承权。
    Linux提供的线程调度算法有三个:轮询、先进先出和其它。其中轮询和先进先出调度算法是POSIX标准所规定,而其他则代表采用Linux自己认为更合适的调度算法,所以默认的调度算法也就是其它了。轮询和先进先出调度算法都属于实时调度算法。轮询指的是时间片轮转,当线程的时间片用完,系统将重新分配时间片,并将它放置在就绪队列尾部,这样可以保证具有相同优先级的轮询任务获得公平的CPU占用时间;先进先出就是先到先服务,一旦线程占用了CPU则一直运行,直到有更高优先级的线程出现或自己放弃。
    设置线程调度算法的接口是pthread_attr_setschedpolicy(),它的完整定义是:
     
    pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
     
    它的第二个参数有三个取值:SCHED_RR(轮询)、SCHED_FIFO(先进先出)和SCHED_OTHER(其它)。
    Linux的线程优先级与进程的优先级不一样,进程优先级我们后面再说。Linux的线程优先级是从1到99的数值,数值越大代表优先级越高。而且要注意的是,只有采用SHCED_RR或SCHED_FIFO调度算法时,优先级才有效。对于采用SCHED_OTHER调度算法的线程,其优先级恒为0。
    设置线程优先级的接口是pthread_attr_setschedparam(),它的完整定义是:
     
    [cpp] view plain copy
     
    1. struct sched_param {  
    2.     int sched_priority;  
    3. }  
    4. int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);  
    sched_param结构体的sched_priority字段就是线程的优先级了。
    此外,即便采用SCHED_RR或SCHED_FIFO调度算法,线程优先级也不是随便就能设置的。首先,进程必须是以root账号运行的;其次,还需要放弃线程的继承权。什么是继承权呢?就是当创建新的线程时,新线程要继承父线程(创建者线程)的调度属性。如果不希望新线程继承父线程的调度属性,就要放弃继承权。
    设置线程继承权的接口是pthread_attr_setinheritsched(),它的完整定义是:
     
    [cpp] view plain copy
     
    1. int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);  
     
    它的第二个参数有两个取值:PTHREAD_INHERIT_SCHED(拥有继承权)和PTHREAD_EXPLICIT_SCHED(放弃继承权)。新线程在默认情况下是拥有继承权。
    代码4能够演示不同调度算法和不同优先级下各线程的行为,同时也展示如何修改线程的调度属性。
    [cpp] view plain copy
     
    1. #include <stdio.h>  
    2. #include <unistd.h>  
    3. #include <stdlib.h>  
    4. #include <pthread.h>  
    5. #define THREAD_COUNT 12  
    6. void show_thread_policy( int threadno )  
    7. {  
    8.     int policy;  
    9.     struct sched_param param;  
    10.     pthread_getschedparam( pthread_self(), &policy, ¶m );  
    11.     switch( policy ){  
    12.     case SCHED_OTHER:  
    13.         printf( "SCHED_OTHER %d ", threadno );  
    14.         break;  
    15.     case SCHED_RR:  
    16.         printf( "SCHDE_RR %d ", threadno );  
    17.         break;  
    18.     case SCHED_FIFO:  
    19.         printf( "SCHED_FIFO %d ", threadno );  
    20.         break;  
    21.     default:  
    22.         printf( "UNKNOWN ");  
    23.     }  
    24. }  
    25. void* thread( void *arg )  
    26. {  
    27.     int i, j;  
    28.     long threadno = (long)arg;  
    29.     printf( "thread %d start ", threadno );  
    30.     sleep(1);  
    31.     show_thread_policy( threadno );  
    32.     for( i = 0; i < 10; ++i ) {  
    33.         for( j = 0; j < 100000000; ++j ){}  
    34.         printf( "thread %d ", threadno );  
    35.     }  
    36.     printf( "thread %d exit ", threadno );  
    37.     return NULL;  
    38. }  
    39. int main( int argc, char *argv[] )  
    40. {  
    41.     long i;  
    42.     pthread_attr_t attr[THREAD_COUNT];  
    43.     pthread_t pth[THREAD_COUNT];  
    44.     struct sched_param param;  
    45.     for( i = 0; i < THREAD_COUNT; ++i )  
    46.         pthread_attr_init( &attr[i] );  
    47.         for( i = 0; i < THREAD_COUNT / 2; ++i ) {  
    48.             param.sched_priority = 10;                    
    49.             pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
    50.             pthread_attr_setschedparam( &attr[i], ¶m );  
    51.             pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
    52.         }  
    53.         for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {  
    54.             param.sched_priority = 20;                    
    55.             pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
    56.             pthread_attr_setschedparam( &attr[i], ¶m );  
    57.             pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
    58.         }  
    59.         for( i = 0; i < THREAD_COUNT; ++i )                      
    60.             pthread_create( &pth[i], &attr[i], thread, (void*)i );                
    61.         for( i = 0; i < THREAD_COUNT; ++i )                      
    62.             pthread_join( pth[i], NULL );                      
    63.         for( i = 0; i < THREAD_COUNT; ++i )                      
    64.             pthread_attr_destroy( &attr[i] );                     
    65.     return 0;                             
    66. }  
    代码4设置线程调度属性
    这段代码中含有一些没有介绍过的接口,读者们可以使用Linux的联机帮助来查看它们的具体用法和作用。

    4.4 堆栈大小属性

     
    从前面的这些例子中可以了解到,线程的主函数与程序的主函数main()有一个很相似的特性,那就是可以拥有局部变量。虽然同一个进程的线程之间是共享内存空间的,但是它的局部变量确并不共享。原因就是局部变量存储在堆栈中,而不同的线程拥有不同的堆栈。Linux系统为每个线程默认分配了8MB的堆栈空间,如果觉得这个空间不够用,可以通过修改线程的堆栈大小属性进行扩容。
    修改线程堆栈大小属性的接口是pthread_attr_setstacksize(),它的完整定义为:
    [cpp] view plain copy
     
    1. int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);  
    它的第二个参数就是堆栈大小了,以字节为单位。需要注意的是,线程堆栈不能小于16KB,而且尽量按4KB(32位系统)或2MB(64位系统)的整数倍分配,也就是内存页面大小的整数倍。此外,修改线程堆栈大小是有风险的,如果你不清楚你在做什么,最好别动它(其实我很后悔把这么危险的东西告诉了你:)。

    4.5 满栈警戒区属性

     
    既然线程是有堆栈的,而且还有大小限制,那么就一定会出现将堆栈用满的情况。线程的堆栈用满是非常危险的事情,因为这可能会导致对内核空间的破坏,一旦被有心人士所利用,后果也不堪设想。为了防治这类事情的发生,Linux为线程堆栈设置了一个满栈警戒区。这个区域一般就是一个页面,属于线程堆栈的一个扩展区域。一旦有代码访问了这个区域,就会发出SIGSEGV信号进行通知。
    虽然满栈警戒区可以起到安全作用,但是也有弊病,就是会白白浪费掉内存空间,对于内存紧张的系统会使系统变得很慢。所有就有了关闭这个警戒区的需求。同时,如果我们修改了线程堆栈的大小,那么系统会认为我们会自己管理堆栈,也会将警戒区取消掉,如果有需要就要开启它。
    修改满栈警戒区属性的接口是pthread_attr_setguardsize(),它的完整定义为:
    [cpp] view plain copy
     
    1. int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);  
    它的第二个参数就是警戒区大小了,以字节为单位。与设置线程堆栈大小属性相仿,应该尽量按照4KB或2MB的整数倍来分配。当设置警戒区大小为0时,就关闭了这个警戒区。
    虽然栈满警戒区需要浪费掉一点内存,但是能够极大的提高安全性,所以这点损失是值得的。而且一旦修改了线程堆栈的大小,一定要记得同时设置这个警戒区。
     
    五、线程终止
     
    pthread_exit函数:
    • 原型: void pthread_exit(void *rval_ptr);
    • 头文件: <pthread.h>
    • 参数: rval_ptr是一个无类型指针, 指向线程的返回值存储变量.

    pthread_join函数:

    • 原型: int pthread_join(pthread_t thread, void **rval_ptr);
    • 头文件: <pthread.h>
    • 返回值: 成功则返回0, 否则返回错误编号.
    • 参数:
      • thread: 线程ID.
      • rval_ptr: 指向返回值的指针(返回值也是个指针).
    • 说明:
      • 调用线程将一直阻塞, 直到指定的线程调用pthread_exit, 从启动例程返回或被取消.
      • 如果线程从它的启动例程返回, rval_ptr包含返回码.
      • 如果线程被取消, 由rval_ptr指定的内存单元置为: PTHREAD_CANCELED.
      • 如果对返回值不关心, 可把rval_ptr设为NULL.

    实例:

     1 #include <pthread.h>
     2 #include <stdio.h>
     3  
     4 /* print process and thread IDs */
     5 void printids(const char *s)
     6 {
     7    pid_t pid, ppid;
     8    pthread_t tid;
     9      pid= getpid();
    10    ppid = getppid();
    11    tid = pthread_self();
    12     printf("%16s pid %5u ppid %5u tid %16u (0x%x) ",
    13             s, (unsigned int)pid, (unsigned int)ppid,
    14             (unsigned int)tid, (unsigned int)tid);
    15 }
    16  /* thread process */
    17 void *thread_func(void *arg);
    18 {
    19    printids("new thread: ");
    20    return (void *)108;
    21 }
    22  /* main func */
    23 int main()
    24 {
    25    int err;
    26    void *tret; /* thread return value */
    27    pthread_t ntid;
    28    err = pthread_create(&ntid, NULL, thread_func, NULL);
    29    if (err != 0)
    30        perror("can't create thread");
    31  
    32    err = pthread_join(ntid, &tret);
    33    if (err != 0)
    34        perror("can't join thread");
    35     printids("main thread: ");
    36    printf("thread exit code: %d ", (int)tret);
    37    sleep(1);
    38    return 0;
    39 }

    pthread_cancel函数:

    pthread_cancel函数发送终止信号

    pthread_setcancelstate函数设置终止方式

    pthread_testcancel函数取消线程(另一功能是:设置取消点)

    1) 线程取消的定义
    一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

    2) 线程取消的语义
    线程取消的方法是向目标线程发Cancel信号(pthread_cancel函数发送Cancel信号),但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态(pthread_setcancelstate函数设置状态)决定。

    线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

    3 )取消点
    根据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();



    4 )程序设计方面的考虑
    如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

    5 )与线程取消相关的pthread函数
    int pthread_cancel(pthread_t thread)
    发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

    int pthread_setcancelstate(int state, int *oldstate)
    设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。

    int pthread_setcanceltype(int type, int *oldtype)
    设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。

    void pthread_testcancel(void)
    功能一:设置取消点;

    功能二:检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

    代码:

      1 #include <stdio.h>
      2 #include <errno.h>
      3 #include <unistd.h>
      4 #include <stdlib.h>
      5 #include <pthread.h>
      6 
      7 
      8 #define THREAD_MAX 4
      9 
     10 
     11 pthread_mutex_t mutex;
     12 pthread_t thread[THREAD_MAX];
     13 
     14 
     15 static int tries;
     16 static int started;
     17 
     18 
     19 void print_it(int *arg)
     20 {
     21 pthread_t tid;
     22 tid = pthread_self();
     23 printf("Thread %lx was canceled on its %d try.
    ",tid,*arg);
     24 }
     25 
     26 
     27 void *Search_Num(int arg)
     28 {
     29 pthread_t tid;
     30 int num;
     31 int k=0,h=0,j;
     32 int ntries;
     33 tid = pthread_self();
     34 
     35 /*while(pthread_mutex_trylock(&mutex) == EBUSY)
     36 {
     37 printf("**************busy****************
    ");
     38 pthread_testcancel();
     39 }*/
     40 srand(arg);
     41 num = rand()&0xFFFFFF;
     42 //pthread_mutex_unlock(&mutex);
     43 
     44 printf("thread num %lx
    ",tid);
     45 
     46 ntries = 0;
     47 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
     48 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
     49 
     50 pthread_cleanup_push((void *)print_it,(void *)&ntries);
     51 
     52 while(1)
     53 {
     54 num = (num+1)&0xffffff;
     55 ntries++;
     56 
     57 if(arg == num)
     58 {
     59 //只允许一个线程操作此处
     60 while(pthread_mutex_trylock(&mutex) == EBUSY) { 
     61 //一个线程操作后其余线程进入次循环挂起,等待pthread_cancel函数发送cancel信号终止线程
     62 k++;
     63 if(k == 10000)
     64 {
     65 printf("----------2busy2-----------
    ");
     66 }
     67 
     68 pthread_testcancel();
     69 }
     70 tries = ntries;
     71 //pthread_mutex_unlock(&mutex);   //如果加上这句话,将会有好几个线程找到主函数中设定的值pid
     72 printf("Thread %lx found the number!
    ",tid);
     73 
     74 for(j = 0;j<THREAD_MAX;j++)
     75 {
     76 if(thread[j]!=tid)
     77 {
     78 pthread_cancel(thread[j]);
     79 }
     80 }
     81 
     82 break;
     83 }
     84 if(ntries%100 == 0)
     85 {
     86 h++;
     87 /*线程阻塞,其他线程争夺资源,或者是等待pthread_cancel函数发送cancel信号终止线程*/
     88 pthread_testcancel();
     89 /*这是为了弄明白pthread_testcancel函数的作用而设置的代码段*/
     90 if(h == 10000)
     91 {
     92 h = 0;
     93 printf("----------thread num %lx-------------
    ",tid);
     94 }
     95 }
     96 }
     97 pthread_cleanup_pop(0);
     98 return (void *)0;
     99 }
    100 
    101 
    102 int main()
    103 {
    104 int i,pid;
    105 
    106 pid = getpid(); //设置要查找的数
    107 
    108 pthread_mutex_init(&mutex,NULL);
    109 printf("Search the num of %d
    ",pid);
    110 for(started = 0; started < THREAD_MAX; started++)
    111 {
    112 pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid);
    113 }
    114 
    115 for(i = 0; i < THREAD_MAX; i++)
    116 {
    117 printf("-----------i = %d--------------
    ",i);
    118 pthread_join(thread[i],NULL);
    119 }
    120 printf("It took %d tries ot find the number!
    ",tries);
    121 return 0;
    122 }
    123 运行结果:
    124 Search the num of 6531
    125 -----------i = 0--------------
    126 thread num b6fbcb70
    127 thread num b67bbb70
    128 thread num b5fbab70
    129 thread num b77bdb70
    130 ----------thread num b67bbb70-------------
    131 Thread b67bbb70 found the number!
    132 ----------thread num b6fbcb70-------------
    133 ----------thread num b77bdb70-------------
    134 ----------2busy2-----------
    135 ----------thread num b5fbab70-------------
    136 ----------2busy2-----------
    137 Thread b5fbab70 was canceled on its 1174527 try.
    138 Thread b77bdb70 was canceled on its 1023100 try.
    139 -----------i = 1--------------
    140 Thread b6fbcb70 was canceled on its 1174527 try.
    141 -----------i = 2--------------
    142 -----------i = 3--------------
    143 It took 1174527 tries ot find the number!
    void pthread_cleanup_push(void (*rtn)(void *),void *arg);
    void pthread_cleanup_pop(int execute);
    当线程执行以下动作时调用清理函数,调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。
    1.调用pthread_exit时。2.响应取消请求时。3.用非零execute参数调用pthread_cleanup_pop时。
    如果execute参数置为0,清理函数将不被调用。无论哪种情况,pthread_cleanup_pop都将删除上次pthread_clean_push调用建立的清理处理程序。
    实例:
     1 <span style="font-size: small;">#include <stdlib.h>  
     2 #include <stdio.h>  
     3 #include <pthread.h>  
     4   
     5 void cleanup(void *arg)  
     6 {  
     7     printf("cleanup: %s
    ", (char *)arg);  
     8 }  
     9   
    10 void *thr_fn1(void *arg)  
    11 {  
    12     printf("thread 1 start
    ");  
    13     pthread_cleanup_push(cleanup, "thread 1 first handler");  
    14     pthread_cleanup_push(cleanup, "thread 1 second handler");  
    15     printf("thread 1 push complete
    ");  
    16     if (arg)  
    17         return((void *)1);  
    18         // pthread_exit((void *)2);  
    19           
    20     pthread_cleanup_pop(0);  
    21     pthread_cleanup_pop(0);  
    22     // return((void *)1);  
    23     pthread_exit((void *)2);  
    24   
    25 }  
    26   
    27 void *thr_fn2(void *arg)  
    28 {  
    29     printf("thread 2 start
    ");  
    30     pthread_cleanup_push(cleanup, "thread 2 first handler");  
    31     pthread_cleanup_push(cleanup, "thread 2 second handler");  
    32     printf("thread 2 push complete
    ");  
    33     if (arg)  
    34         pthread_exit((void *)2);  
    35     pthread_cleanup_pop(0);  
    36     pthread_cleanup_pop(0);  
    37     pthread_exit((void *)2);  
    38 }  
    39   
    40 int main(void)  
    41 {  
    42     int         err;  
    43     pthread_t   tid1, tid2;  
    44     void        *tret;  
    45   
    46     err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);  
    47     if (err != 0)  
    48         printf("can't create thread 1: %c
    ", strerror(err));  
    49     err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);  
    50     if (err != 0)  
    51         printf("can't create thread 2: %c
    ", strerror(err));  
    52     err = pthread_join(tid1, &tret);  
    53     if (err != 0)  
    54         printf("can't join with thread 1: %c
    ", strerror(err));  
    55     printf("thread 1 exit code %d
    ", (int)tret);  
    56     err = pthread_join(tid2, &tret);  
    57     if (err != 0)  
    58         printf("can't join with thread 2: %c
    ", strerror(err));  
    59     printf("thread 2 exit code %d
    ", (int)tret);  
    60     exit(0);  
    61 }  
    62 </span>  

    pthread_detach()函数:

    创建一个线程默认的状态是joinable。

    如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码).

    所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代 码,回收其资源(类似于wait,waitpid) 。

    但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。

    比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以

    1)在子线程中加入代码pthread_detach(pthread_self())

    2)父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)   

    这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

    pthread_kill()函数:

    pthread_kill与kill有区别,是向线程发送signal。,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。

    int pthread_kill(pthread_t thread, int sig);

    向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出

    pthread_kill(threadid, SIGKILL)杀死整个进程。 如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)。所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。

    如果int sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着。pthread_kill的返回值: 成功:0 线程不存在:ESRCH 信号不合法:EINVAL

    代码:

    复制代码
    int kill_rc = pthread_kill(thread_id,0);
    
    if(kill_rc == ESRCH) 
            printf("the specified thread did not exists or already quit
    "); 
    
    else if(kill_rc == EINVAL) 
            printf("signal is invalid
    "); 
    else 
            printf("the specified thread is alive
    ");
    复制代码

    这里附上线程基本函数:

    ------------------------------------------------------------------------------------------
                         POSIX函数                                                    描述
    -------------------------------------------------------------------------------------------
                        pthread_create                                    创建一个线程
                        pthread_self                                        找出自己的线程ID
                        pthread_equal                                     测试2个线程ID是否相等
                        pthread_detach                                   设置线程以释放资源
                        pthread_join                                        等待一个线程
                        pthread_cancel                                    终止另一个线程
                        pthread_exit                                        退出线程,而不退出进程
                        pthread_kill                                         向线程发送一个信号
    -------------------------------------------------------------------------------------------
     
     
     

    线程属性pthread_attr_t简介

    Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。在pthread_create中,把第二个参数设置为NULL的话,将采用默认的属性配置。
    pthread_attr_t的主要属性的意义如下:
    __detachstate,表示新线程是否与进程中其他线程脱离同步, 如果设置为PTHREAD_CREATE_DETACHED 则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
    __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。
    __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。
    __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
    __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
      为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_getXXX/pthread_attr_setXXX函数。
    在设置线程属性 pthread_attr_t 之前,通常先调用pthread_attr_init来初始化,之后来调用相应的属性设置函数。
    主要的函数如下:
    1、pthread_attr_init
    功能:        对线程属性变量的初始化。
    头文件:     <pthread.h>
    函数原型:   int pthread_attr_init (pthread_attr_t* attr);
    函数传入值:attr:线程属性。
    函数返回值:成功: 0
                    失败: -1
    2、pthread_attr_setscope
    功能:        设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
    头文件:     <pthread.h>
    函数原型:   int pthread_attr_setscope (pthread_attr_t* attr, int scope);
    函数传入值:attr: 线程属性。
                          scope:PTHREAD_SCOPE_SYSTEM,表示与系统中所有线程一起竞争CPU时间,
                                     PTHREAD_SCOPE_PROCESS,表示仅与同进程中的线程竞争CPU
    函数返回值得:同1。
    3、pthread_attr_setdetachstate
    功能:        设置线程detachstate属性。该表示新线程是否与进程中其他线程脱离同步,如果设置为PTHREAD_CREATE_DETACHED则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
    头文件:      <phread.h>
    函数原型:    int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
    函数传入值:attr:线程属性。
    detachstate:PTHREAD_CREATE_DETACHED,不能用pthread_join()来同步,且在退出时自行释放所占用的资源
                        PTHREAD_CREATE_JOINABLE,能用pthread_join()来同步
    函数返回值得:同1。
    4、pthread_attr_setschedparam
    功能:       设置线程schedparam属性,即调用的优先级。
    头文件:     <pthread.h>
    函数原型:   int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
    函数传入值:attr:线程属性。
                     param:线程优先级。一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0
    函数返回值:同1。
    5、pthread_attr_getschedparam
    功能:       得到线程优先级。
    头文件:    <pthread.h>
    函数原型:  int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
    函数传入值:attr:线程属性;
                        param:线程优先级;
    函数返回值:同1。
     
    例:
     1 #include <stdlib.h>   
     2 #include <stdio.h>   
     3 #include <errno.h>   
     4 #include <pthread.h>   
     5 static void pthread_func_1 (void);   
     6 static void pthread_func_2 (void);   
     7   
     8 int main (int argc, char** argv)   
     9 {   
    10   pthread_t pt_1 = 0;   
    11   pthread_t pt_2 = 0;   
    12   pthread_attr_t atrr = {0};   
    13   int ret = 0;   
    14   
    15 /*初始化属性线程属性*/
    16   pthread_attr_init (&attr);   
    17   pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);   
    18   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);   
    19      
    20   ret = pthread_create (&pt_1, &attr, pthread_func_1, NULL);   
    21   if (ret != 0)   
    22   {   
    23     perror ("pthread_1_create");   
    24   }   
    25      
    26   ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL);   
    27   if (ret != 0)   
    28   {   
    29     perror ("pthread_2_create");   
    30   }   
    31   
    32   pthread_join (pt_2, NULL);   
    33   
    34   return 0;   
    35 }   
    36   
    37 static void pthread_func_1 (void)   
    38 {   
    39   int i = 0;   
    40      
    41   for (; i < 6; i++)   
    42   {    
    43     printf ("This is pthread_1.
    ");   
    44       
    45     if (i == 2)   
    46     {   
    47       pthread_exit (0);   
    48     }   
    49   }   
    50   
    51   return;   
    52 }   
    53   
    54 static void pthread_func_2 (void)   
    55 {   
    56   int i = 0;   
    57   
    58   for (; i < 3; i ++)   
    59   {   
    60     printf ("This is pthread_2.
    ");   
    61   }   
    62   
    63   return;   
    64 }  
     
     
  • 相关阅读:
    华为机试题01背包问题
    丑数
    动态规划(1)
    Linux 后台启动 Redis
    redis.exceptions.ResponseError: MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk.
    SQLServer从渣仔到小白
    cmder 增强型命令行工具
    总结在部署分布式爬虫环境过程中常见的若干问题
    【pymongo.errors】Cursor not found
    浅析scrapy与scrapy_redis区别
  • 原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/6962868.html
Copyright © 2011-2022 走看看