Liunx多线程pthread初探

来源:互联网 发布:淘宝卖家设置评论有礼 编辑:程序博客网 时间:2024/06/10 13:22

一、线程标识

  • 线程有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)指针参数, 如要传多个参数, 可以用结构封装.
    •  因为pthread的库不是linux系统的库,所以在进行编译的时候要加上     -lpthread
    • 也可以自己在编译器里设置好pthread的路径,然后就可以直接通过编译器编译了
      #include<iostream>#include<stdio.h>#include<pthread.h>#include<unistd.h>using namespace std;void *func1(void *ptr){    for(int i=0; i<5; i++)    {        printf("This is thread1!\n");        sleep(1);    }}void *func2(void *ptr){    for(int i=0; i<5; i++)    {        printf("This is thread2!\n");        sleep(1);    }}int main(){    pthread_t id1,id2;    int thread1 = pthread_create(&id1,NULL,func1,NULL);    if(thread1)    {        printf("Create thread1 error!\n");        return 1;    }    int thread2 = pthread_create(&id2,NULL,func2,NULL);    if(thread2)    {        printf("Create thread2 error!\n");        return 1;    }    pthread_join(id1,NULL);    pthread_join(id2,NULL);    return 0;}

    向线程传递参数

    #include<iostream>#include<stdio.h>#include<pthread.h>#include<unistd.h>using namespace std;void *func(void *ptr){    int *num = (int*)ptr;    printf("a = %d\n",*num);}int main(){    int a = 666;    int *p = &a;    pthread_t id;    int thread = pthread_create(&id,NULL,func,p);    if(thread)    {        printf("Create thread error!\n");        return 1;    }    sleep(1);    printf("Thread is created!\n");    return 0;}

三、线程属性

 pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:

 __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用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一值。

 

pthread_attr_t结构中还有一些值,为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、 pthread_attr_destroy()和与各个属性相关的pthread_attr_get(),pthread_attr_set()函数。

 

  pthread_create()中,第二个参数(pthread_attr_t)为将要创建的thread属性。通常情况下配置为NULL,使用缺省设置就可以了。但了解这些属性,有利于更好的理解thread.

属性对象(pthread_attr_t)是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。

 创建属性:

int pthread_attr_init(pthread_attr_t *attr);

创建的属性设定为缺省设置。

 销毁属性:

int pthread_attr_destroy(pthread_attr_t *attr);


设置分离状态:

线程的分离状态有2种:PTHREAD_CREATE_JOINABLE(非分离状态), PTHREAD_CREATE_DETACHED(分离状态)

分离状态含义如下:

如果使用 PTHREAD_CREATE_JOINABLE 创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行 pthread_join。 非分离线程在终止后,必须要有一个线程用 join 来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。

 

如果使用 PTHREAD_CREATE_DETACHED 创建分离thread,则表明此thread在退出时会自动回收资源和thread ID.

 

Sam之前很喜欢使用分离thread. 但现在慢慢使用中觉得这样是个不好的习惯。因为分离thread有个问题:主程序退出时,很难确认子thread已经退出。只好使用全局变量来标明子thread已经正常退出了。

另外:不管创建分离还是非分离的thread.在子thread全部退出之前退出主程序都是很有风险的。如果主thread选择return,或者调用exit()退出,则所有thread都会被kill掉。这样很容易出错。Sam上次出的问题其实就是这个。但如果主thread只是调用pthread_exit().则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。

 

intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

得到当前和分离状态和设置当前的分离状态。

 

设置栈溢出保护区大小:

栈溢出概念:

·                     溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。

·                     线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。

int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);

设置和得到栈溢出保护区。如果guardsize设为0。则表示不设置栈溢出保护区。guardsize 的值向上舍入为PAGESIZE 的倍数。

 

设置thread竞用范围:

竞用范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)指 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。

 

int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

 

设置线程并行级别:

int pthread_getconcurrency(void);
int pthread_setconcurrency(int new_level);

 

设置调度策略:

POSIX 标准指定 SCHED_FIFO(先入先出)、SCHED_RR(循环)或 SCHED_OTHER(实现定义的方法)的调度策略属性。

·                     SCHED_FIFO

如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用 SCHED_FIFOSCHED_FIFO 基于 TS 调度类。

·                     SCHED_RR

如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户ID 0

SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。

 

int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

 

设置优先级:

int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);

int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
              conststruct sched_param *restrict param);

比较复杂,Sam没去研究。

 

设置栈大小:

当创建一个thread时,会给它分配一个栈空间,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端(第二项设置中设置)。

指定栈时,还应使用 PTHREAD_CREATE_JOINABLE 创建线程。在该线程的 pthread_join() 调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join

一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配指定大小的虚拟内存。

#ulimit -a可以看到这个缺省大小




 四、线程终止

 

如果进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。

单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。

(1):从启动例程中返回,返回值是线程的退出码

(2):线程可以被同一进程中的其他线程取消

(3):线程调用pthread_exit()

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.