Linux C 系统编程(10)线程管理 基本操作

1 线程概述

1.1 线程简介

在linux环境下,所谓线程即轻量级的进程。操作系统以进程为单位分配资源,在一个执行空间内使用多个小型进程并发完成不同的任务,这种小型进程称之为线程。在同一进程的线程中,有共享的资源,也有私有的资源,如下:

  1. 共享资源:地址空间、全局变量、打开的文件、子进程、闹铃、信号以及信号服务程序、记帐信息
  2. 私有资源:程序计数器、寄存器、栈、状态字。

1.2 现代操作系统的线程实现模型

现代操作系统的线程模型是基于以前的用户态线程模型和内核态线程模型,也是两者的结合。用户态的执行系统负责进程内部线程在非阻塞时的切换;内核态的操作系统负责阻塞线程的切换。内核态线程较少,用户态线程较多,每个内核态线程为一个/多个用户态线程提供服务。在分配线程时,一般将需要执行阻塞操作的线程设置为内核态线程,将不会进行阻塞操作的线程设置为用户态线程

1.3 多线程的优势

增加了程序的并发性,从而提高程序的运行效率。

1.4 线程编程需要注意的地方

  1. pthread的这套函数用man手册是不管用的,因为是单独的pthread库。
  2. pthread相关的函数都在头文件pthread.h文件中。
  3. 直接编译时一般会出错,要加上库-lpthread。    

2 线程标识符

每个线程都有自己的ID,使用数据类型pthread_t来表示,本质上是一个无符号整型,即typedef unsigned int pthread_t;出于移植性的考虑,不可以对pthread_t类型等价于无符号整型类型操作。

2.1 pthread_self函数

linux下使用pthread_self函数得到一个线程的ID

pthread_t pthread_self(void);
函数返回本线程的线程ID。

2.2 pthread_equal函数

linux下使用pthread_equal函数比较两个线程ID是否相同

int pthread_equal(pthread_t tid1,pthread_t tid2);
参数tid1\tid2:表示将要比较的量个线程ID号。
如果两个值相同,返回0,不相等则返回非零值。

3 线程的创建     pthread_creat函数

linux下使用pthread_creat函数来创建一个线程:

int pthread_create (pthread_t * restrict tidp , pthread_attr_t *restrict \
attr,void *(*start_routine) (void *),void *restrict arg);

参数tidp:pthread_t类型的指针,一个值结果参数。
参数attr:线程的属性,不想指定特定属性的时候置为NULL。
参数start_rtn:一个函数指针(返回值是一个指向void型的指针,参数也是一个指向void型的指针),创建的线程要从该函数的起始位置处开始执行,函数返回该线程就停止了。
参数arg:一个void型的指针,是函数start_rtn的参数,在线程开始执行时,该参数由内核传递给线程。
函数执行成功返回0,失败返回错误编号。这种规律适合于所有的线程系列函数。

注意:

  1. linux下的线程和进程拥有平等的调度权利,一个线程和另一个线程的调度顺序是完全不能够预测的,这依赖于内核的调度算法。
  2. 如果要给pthread_creat函数传递多个参数则需要将所有参数组织在一个结构体内,再将结构体的地址作为参数传递给新的线程。
  3. 线程系列函数一般不设置errno的值,而是采用返回出错号。因为线程可以随意的访问进程的环境变量,所以当多个线程出错时,errno的值将被多次覆盖,而进程只能检查到最后一个出错线程的出错原因。         

4 线程的终止

4.1 终止线程有3种方式

  1. 线程函数执行结束:使用pthread_creat创建的线程执行一个函数,若该函数执行结束则退出线程。类似于main函数的返回。
  2. 被另外一个线程取消:类似于进程间被kill掉。
  3. 线程自行退出:类似于调用了exit函数。

4.2 pthread_exit函数

linux下使用pthread_exit函数终止线程:

void pthread_exit(void *rval_ptr);
参数rval_ptr:一个指向void型的指针,该指针指向的区域存储退出信息,该信息类似于传递给新线程的参数,将多个信息组织成一个结构体。
函数无返回值。

一个线程的结束信息有两种:

  1. 线程体函数返回的指针所指向的那个区域。取得线程体函数的返回值。
  2. pthread_exit函数所指向的区域。得到pthread_exit函数所设置的退出信息。

4.3 pthread_join函数

linux下使用pthread_join访问一个指定线程的结束信息:

int pthread_join(pthread_t tid,void **rval_ptr);
参数tid:需要取得结束信息的线程。(如果该线程正在运行,则等待,直到该线程结束执行为止;如果指定线程的ID和调用线程的ID不属于同一进程,则出错)。
参数rval_ptr:一个指向void型的指针(因为要在内核中改变它的值)
//如果线程由于线程体函数返回/调用pthread_exit函数退出,则参数指向的是退出信息的首地址。
//如果其他线程由于被其他线程取消而退出则该参数被置为PTHREAD_CANCELED常量。
//如果不关心返回值,则将参数置为NULL。这时仅仅等待线程执行结束,不获得线程退出信息。
函数执行成功返回0,失败返回错误号。

一般的线程使用模型为:

pthread_create();          //创建第1个线程
pthread_create();          //创建第2个线程
...
pthread_create();          //创建第n个线程
...
...
pthread_join();          //得到第1个线程的退出信息
pthread_join();          //得到第2个线程的退出信息
...
pthread_join();          //得到第n个线程的退出信息

4.4 正确退出信息的方式

在线程结束运行后,linux内核保存的只是存储退出信息内存区域的首地址,并未将退出信息保存在内核中,一般使用动态分配的内存/全局变量来保存线程退出的信息。

4.5 取消一个线程的运行 pthread_cancel

一个线程可以被另外一个线程取消。linux下使用pthread_cancel函数取消另一个线程:

int pthread_cancel(pthread_t tid);
参数tid:要取消线程的线程ID。
函数执行成功返回0,失败返回错误编号。

调用pthread_cancel函数等价于被取消的线程自己调用:pthread_exit(PTHREAD_CANCELED);

4.6 线程清理函数     pthread_cleanup_push/pthread_cleanup_pop

linux下使用pthread_cleanup_push/pthread_cleanup_pop实现线程结束的清理:

void pthread_cleanup_push(void (*rtn)(void *),void *arg);          //设置清理程序    
void pthread_cleanup_pop(int execute);                              //执行清理程序
参数rtn: 处理程序入口地址。
参数arg: 传递给处理函数的参数。
参数execute:
0:不执行清理程序。但是将栈顶的清理程序记录出栈。
非0:执行栈顶清理程序。执行后栈顶的清理程序记录出栈。    
函数无返回值。

注意:pthread_cleanup_push函数会在3种情况下执行:

  1. 调用pthread_exit函数。
  2. 被其他线程取消。
  3. 使用非0值参数调用pthread_cleanup_pop函数时。

pthread_push和pthread_pop要成对使用,否则会出错。因为pthread_cleanup_push() 和 pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

#define pthread_cleanup_push(routine,arg) \
{
     struct _pthread_cleanup_buffer _buffer; \
     _pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
     _pthread_cleanup_pop (&_buffer, (execute)); \
    }

可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}"。设置清理程序和执行清理程序的顺序正好是相反的。

4.7 线程分离pthread_detach函数

linux下使用pthread_detach函数实现线程的动态分离:

int pthread_detach(pthread tid);
参数tid:要使之处于分离状态的线程ID。

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


5 线程同步_互斥量     pthread_mutex_***

互斥量是一种锁,在访问共享资源的时候加锁,在结束的时候释放锁,这样保证在任意时间内只有一个线程处于临界区内。任何一个想要进入临界区的想爱你成都要对锁进行测试,如果该锁已经被某一个线程所持有,则测试线程会被阻塞,直到该锁被释放,线程重复上述过程。

5.1 初始化和销毁互斥量

linux环境下使用pthread_mutex_t数据类型表示互斥量,在使用的时候需要初始化。当不用的时候要对其进行销毁。

pthread_mutex_init:锁的初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr)
pthread_mutex_destroy:锁的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数mutex:互斥锁的指针,一种特殊的类型参数。
参数mutexattr:锁的属性,缺省值为NULL。
函数执行成功返回0,失败返回错误号。

有两种方法创建互斥锁:静态和动态。POSIX 定义了一个宏 PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁,如下:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
/*(在 LinuxThreads 实 现 中 , pthread_mutex_t 是 一 个 结 构 ,\
 而PTHREAD_MUTEX_INITIALIZER 则是一个结构常量)*/

动态方式是采用 pthread_mutex_init()函数来初始化互斥锁,API定 义 如 下 :

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 

其中 mutexattr 用于指定互斥锁属性,如果为 NULL 则使用缺省属性。

5.2 得到与释放互斥量

在使用的时候对其进行加锁。在锁的操作结束时对其进行解锁。涉及到的函数有:

//pthread_mutex_lock:加锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex);
//pthread_mutex_trylock:加锁操作
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//pthread_mutex_unlock:解锁操作
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数mutex:互斥锁的指针,一种特殊的类型参数。
函数执行成功返回0,失败返回错误号。

pthread_mutex_lock和pthread_mutex_trylock函数二者的不同之处:

  1. 前者请求的锁如果被某一线程得到,则会阻塞,直到互斥量被释放。
  2. 后者请求的锁如果被某一线程得到,则不会阻塞,立即返回一个错误编号EBUSY,表示申请的锁处于繁忙状态。

6 线程同步_读写锁 pthread_rwlock_***

互斥锁每次只有一个线程得到锁进行操作,其他线程都因为得不到锁而阻塞。而创建多线程进行操作的本意是为了并发执行任务,但是由于互斥锁的缘故导致线程的操作变成了串行的,程序运行的效率会降低。在程序执行时,如果对共享资源进行读操作的线程远多于写线程的时候,使用这种读写锁可以大大提高线程的并发度,从而提高线程的运行效率。

6.1 初始化与销毁读写锁

linux环境下使用pthread_rwlock_t数据类型表示读写锁的类型,在使用的时候需要初始化。当不用的时候要对其进行销毁。

//pthread_rwlock_init:初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);  
//pthread_rwlock_destroy:销毁读写锁  
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
参数rwlock:读写锁的指针。
参数attr:读写锁的属性,缺省值为NULL。
函数执行成功返回0,失败返回错误号。

6.2 得到与释放读写锁

//pthread_rwlock_rdlock:设定读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);      
//pthread_rwlock_wrlock:设定写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);   
参数rwlock:读写锁的指针。
函数执行成功返回0,失败返回错误号。

实现上可能会对读写锁中读模式的锁锁住次数有一定的限制,所以我们需要检查返回值,以确定是否成功。而其他的两个函数会返回错误,但是只要我们的锁设计的恰当,我们可以不必做检查。  非阻塞的函数为:

//pthread_rwlock_rdlock:设定读锁    
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);  
//pthread_rwlock_wrlock:设定写锁    
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
参数rwlock:读写锁的指针。
函数执行成功返回0,失败返回错误号。
//当锁成功获取时,返回0,否则返回EBUSY。
//这两个函数可以避免死锁。如果针对未初始化的读写锁调用进行读写操作,则结果是不确定的。

pthread_rwlock_unlock:释放读写锁 (用来释放在 rwlock 引用的读写锁对象中持有的锁。)

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);    
参数rwlock:读写锁的指针。
函数执行成功返回0,失败返回错误号。

7 线程同步_条件变量 pthread_cond_***

7.1 条件变量的初始化和销毁

条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化。

//pthread_cond_init:条件变量初始化
int pthread_cond_init(pthread_cond_t *restrict cond,pthread_condattr_t *restrict attr);
//pthread_cond_destroy:条件变量销毁
int pthread_cond_destroy(pthread_cond_t *cond);
参数cond:条件变量,一个特殊的参数类型。
参数attr:条件变量的属性,缺省值为NULL。    
函数执行成功则返回0, 出错则返回错误编号。

初始化条件变量的两种方式:

  1. 静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量。
  2. 动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理。

7.2 条件变量的等待条件

等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数。函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的。这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化。当pthread_cond_wait返回时, 互斥量再次被锁住。

//pthread_cond_wait:阻塞等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
//pthread_cond_timewait:超时等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
参数cond:条件变量,一个特殊的参数类型。
参数mutex:等待的指定的锁,一般在其他线程内。
参数timeout:等待的时间。
函数执行成功则返回0, 出错则返回错误编号。

两个函数的区别:两者工作方式相似,只是pthread_cond_timedwait函数多了一个timeout,指定了超时的时间。

7.3 条件变量的通知条件

下面这两个函数用于通知线程条件已经满足,调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号。

pthread_cond_signal:
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast:
int pthread_cond_broadcast(pthread_cond_t *cond);
参数cond:条件变量,一个特殊的参数类型。
成功则返回0, 出错则返回错误编号。

两个函数的区别:

  1. pthread_cond_signal是将唤醒等待该条件的某个线程。
  2. pthread_cond_broadcast是将唤醒等待该条件的所有线程。
     
发布了289 篇原创文章 · 获赞 47 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/vviccc/article/details/105165155