Linux C多线程编程基础

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_37653144/article/details/81981203

获取线程标识符

       每一个线程都有一个在进程中唯一的线程标识符,在Linux中用一个数据类型pthread_t来表示,实际上这个类型是一个机器相关的无符号整型数据。

       Linux提供了两个函数用于获取和比较线程标识符。

#inlcude <pthread.h>

pthread_t pthread_self(void);    //返回值是线程自身的线程标识符
int pthread_equal(pthread_t tid1, pthread_t tid2);    //比较两个线程标识符是否相等,若相等则返回一个非0值,若不相等则返回0

线程操作

       与进程类似,线程的基本操作包括创建、退出、终止等。

创建线程

       Linux提供pthread_create函数用于创建一个线程,如果创建成功则返回0,否则返回错误编号。

#include <pthread.h>

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void*(*start_routine)(void *), void *arg);

       该函数的参数说明如下:

       ·thread:线程的标识符,需要说明的是这个参数并不是用户确定的,用户只需声明一个pthread_t类型的变量,并将一个指向该变量的指针作为实参传递给pthread_create函数,函数在创建线程时会将新线程的线程标识符放到这个变量中。

       ·attr:指定线程的属性,也可以将其设置为NULL。

       ·start_routine:用于指定开始运行的函数,新创建的线程时从这个函数开始运行的,用户需要指定这个函数。

       ·arg:函数start_routine所需要的参数,如果需要传递的参数不止一个,则需要将这些参数都放到一个结构中,然后将这个结构的地址传给arg

线程属性

       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;

       在使用一个线程属性对象之前,必须对其进行初始化,pthread_attr_init函数用于完成初始化;在使用完一个线程属性对象后,必须对其进行销毁,pthread_attr_destroy函数用于完成销毁工作。

#include <pthread.h>

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

       线程属性对象中的线程堆栈大小可由pthread_attr_setstacksize和pthread_attr_getstacksize分别设置和获取。

#include <pthread.h>

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

        同样的,线程属性对象中的成员都可以通过对应函数进行设置和获取。

阻塞和退出线程

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
void pthread_exit(void *retval);

       pthread_join函数可阻塞指定线程,等待该线程的结束并回收其资源(否则会产生僵尸线程)。参数thread是线程标识符,用于指定要阻塞的进程;参数retval用于存放指定线程的返回值,对于每一个可连接的线程都必须调用该函数一次。

       pthread_exit函数用于退出当前进程,函数没有返回值,参数retval是线程的终止状态(可使用pthread_join函数来获取)。

       需要注意的是,调用pthread_exit相当于exit系统调用函数,属于线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。

取消和清理线程

       pthread_cancel函数会发送终止信号给指定线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。 若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。

#include <pthread.h>

int pthread_cancel(pthread_t thread);

       特别地,若线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。因此需要一个机制来妥善处理资源释放的问题。

       在调用pthread_cancel函数取消一个线程后,需要调用相应的函数对进程退出之后的环境进行清理,这些函数被称为线程清理程序(Thread Cleanup Handler),线程可以建立多个清理程序。

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

       pthread_cleanup_push函数将子程序routine连同它的参数arg一起压入当前线程的cleanup处理程序的堆栈中;当当前线程调用pthread_exit或通过pthread_cancel终止执行时,堆栈中的处理程序将按照压栈时相反的顺序调用。函数pthread_cleanup_pop则从堆栈中弹出栈顶的处理程序并执行它。

       需要注意的是,实际上真正对线程执行清理工作的是在pthread_cleanup_push中作为参数传递进去的routine函数,其参数通过arg传递进去。这些处理程序在线程执行pthread_exit函数或是利用execute参数调用pthread_cleanup_pop函数的时候亦或是响应取消请求时被调用。

分离线程

       在Linux中,线程一般有分离和非分离两种状态(默认情况下是非分离状态)。父线程维护子线程的某些信息并等待子线程的结束,在没有调用join的情况下,子线程结束时,父线程维护的信息可能没有得到及时释放,如果父线程中有大量非分离状态的子线程(Linux中使用pthread_create函数创建的线程都属于这种),可能会出现堆栈空间不足的错误(错误返回值12)。而对于分离线程来说,不会有其他线程等待它的结束,运行结束后,线程终止,资源立即释放。

       在Linux内核中,可以调用pthread_create函数来进行线程分离。

#include <pthread.h>

int pthread_detach(pthread_t thread);    //若函数调用成功则返回0,否则返回错误编号

线程私有数据

       每个线程都有一些属于自己的数据,当线程对这些数据进行操作时可以独立地访问它们,而不用担心其他线程和自己争夺所有权,这种数据被称为线程私有数据。线程私有数据实现的主要思想是:在分配线程私有数据之前,创建与该数据相关联的健,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。

       系统为每个进程维护了一个称之为Key结构的结构数组:

       在上图中Key 结构的“标志”指示这个数据元素是否正在使用。在刚开始时所有的标志初始化为“不在使用”。当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统会搜索Key结构数组,找出第一个“不在使用”的元素,并把该元素的索引(称为“键”),返回给调用线程。

创建键函数

       在分配线程私有数据之前,需要创建和该数据相关联的键,这个键用于获取对线程私有数据的访问权。用户可以使用pthread_key_create函数来创建一个键:

#include <pthread.h>

int pthread_key_create(pthread_key_t *key, void (*dest_routine(void *)));

       该函数用于创建一个对进程中所有线程都可见的关键字,这个关键字可以通过函数pthread_setspecific和pthread_getspecific来设置和读取。

       除了创建键以外,pthread_key_create可以选择为该键关联一个析构函数,当线程退出时,如果数据地址已经被置为一个非NULL的值,则这个析构函数会被调用。线程可以为线程私有数据创建多个键,每个键都可以由一个析构函数与其关联,各个键的析构函数可以互不相同。

键关联函数

       当键被创建之后,可以调用pthread_setspecific函数将线程私有数据与键关联。

#include <pthread.h>

int pthread_setspecific(pthread_key_t key, const void *pointer);

        函数指定由参数pointer指定的指针指向由参数key指定的关键字,每一个线程都有一个互相独立的指针,这个指针指向一个特定的关键字。

取消键关联函数

       对于用户而言,可以调用pthread_key_delete来取消键与线程私有数据值之间的关联。

#include <pthread.h>

int pthread_key_delete(pthread_key_t key);

       需要注意的是,这个函数在调用的时候并不会激活与键关联的析构函数。

解决键冲突函数

       有些线程可能看到某个键值,而其他的线程看到的则是另外一个不同的值,这是一种竞争,如果需要解决这种竞争可以使用pthread_once函数。

#include <pthread.h>

void *pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

       pthread_once函数用于保证某些初始化代码至多只能执行一次,参数once_control指向静态的或外部的变量,这个变量初始化为PTHREAD_ONCE_INIT。当第一次调用pthread_once时,系统将记录已经执行了初始化,后面再调用pthread_once时,如果参数once_control相同,那么什么也不做。

线程私有数据地址获取函数   

#include <pthread.h>

void *pthread_getspecific(pthread_key_t key);

                                                                                                    本文部分内容摘自《Linux C编程从基础到实践》

猜你喜欢

转载自blog.csdn.net/qq_37653144/article/details/81981203