linux线程


线程共享的资源

  • 文件描述符表
  • 每种信号的处理方式
  • 当前工作目录
  • 用户ID和组ID
  • 内存空间(除了栈区)

线程非共享资源

  • 线程ID
  • 函数运行上下文(各种寄存器的值),栈指针
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

线程常用操作

线程号:pthread_self()

#include 
pthread_t pthread_self();
  • 功能:返回线程号
  • 返回值:线程号

判断是否是一个线程: pthread_equal()

int pthread_equal(pthread_t t1, pthread_t t2);
  • 功能:
    判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
  • 返回值:
    相等:非 0
    不相等:0

创建线程:pthread_creat()

#include 
int pthread_create(pthread_t *thread,
            const pthread_attr_t *attr,
            void *(*start_routine)(void *),
            void *arg );
  • 参数:
    thread:线程标识符地址。
    attr:线程属性结构体地址,通常设置为 NULL。
    start_routine:线程函数的入口地址。
    arg:传给线程函数的参数。
  • 返回值:
    成功:0
    失败:非 0

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。

由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印。

线程资源回收:pthread_join()

#include 
int pthread_join(pthread_t thread, void **retval);
  • 功能:
    等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。
  • 参数:
    thread:被等待的线程号。
    retval:用来存储线程退出状态的指针的地址。
  • 返回值:
    成功:0
    失败:非 0

线程分离:pthread_detach()

#include 
int pthread_detach(pthread_t thread);
  • 功能:
    使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
  • 返回值:
    成功:0
    失败:非0

线程退出:pthread_exit()

#include 
void pthread_exit(void *retval);  
  • 功能:
    退出调用线程。
  • 参数:
    retval:存储线程退出状态的指针。

线程取消:pthread_cancel()

#include 
int pthread_cancel(pthread_t thread);
  • 功能:
    杀死(取消)线程
  • 参数:
    thread : 目标线程ID。
  • 返回值:
    成功:0
    失败:出错编号

注意:线程的取消有一定的延时。需要等待线程到达某个取消点(检查点)。类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。

取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。

可粗略认为一个系统调用(进入内核)即为一个取消点。

线程属性

typedef struct
{
    int             etachstate;     //线程的分离状态
    int             schedpolicy;    //线程调度策略
    struct sched_param  schedparam; //线程的调度参数
    int             inheritsched;   //线程的继承性
    int             scope;      //线程的作用域
    size_t          guardsize;  //线程栈末尾的警戒缓冲区大小
    int             stackaddr_set; //线程的栈设置
    void*           stackaddr;  //线程栈的位置
    size_t          stacksize;  //线程栈的大小
} pthread_attr_t;

主要结构体成员:

  1. 线程分离状态

  2. 线程栈大小(默认平均分配)

  3. 线程栈警戒缓冲区大小(位于栈末尾)

  4. 线程栈最低地址

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。

线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

线程属性初始化和销毁

#include 
int pthread_attr_init(pthread_attr_t *attr);
功能:
    初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程
参数:
    attr:线程属性结构体
返回值:
    成功:0
    失败:错误号

int pthread_attr_destroy(pthread_attr_t *attr);
功能:
    销毁线程属性所占用的资源函数
参数:
    attr:线程属性结构体
返回值:
    成功:0
    失败:错误号

线程分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。

  • 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
  • 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
#include 
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:
    attr:已初始化的线程属性
    detachstate:    分离状态
        PTHREAD_CREATE_DETACHED(分离线程)
        PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:
    成功:0
    失败:非0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:
    attr:已初始化的线程属性
    detachstate:    分离状态
        PTHREAD_CREATE_DETACHED(分离线程)
        PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:
    成功:0
    失败:非0

这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。

要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。

设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

线程使用注意事项

  • 主线程退出其他线程不退出,主线程应调用`pthread_exit

  • 避免僵尸线程

    • pthread_join
    • pthread_detach
    • pthread_create指定分离属性

    被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

  • mallocmmap申请的内存可以被其他线程释放

  • 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程t在子进程中均pthread_exit

  • 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制