[APUE chapter 12] 线程控制

作者:isshe
日期:2016.10.30
邮箱:i.sshe@outlook.com
github: https://github.com/isshe

1.前言

2. 相关概念

  • 线程分离:
    • 就是说与创建线程分离,当新建线程运行结束,就终止线程并释放资源。
    • 默认情况下是不线程分离的,此时新线程运行结束后如果创建线程没有结束就等待创建线程结束,或者使用pthread_join()才能终止新线程,并回收资源。
  • 线程分离方法:(通常在不关心线程终止状态时候使用)
    • 使用pthread_detach函数,线程退出时回收资源。
    • 从一开始就设置线程属性为线程分离:修改pthread_attr_t结构中的detachstate线程属性。

2.1 线程属性

  • 在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE来检查系统是否支持线程栈属性
  • 在运行阶段使用_SC_THREAD_ATTR_STACKADDR 和 _SC_THREAD_ATTR_STACKSIZE参数调用sysconf检查系统是否支持线程栈属性。
  • 当修改线程属性stackaddr时,会使警戒区无效,即guardsize==0。
  • 互斥量用于保护条件变量关联的条件。在阻塞线程之前,pthread_cond_wait和pthread_cond_timewait函数释放与条件相关的互斥量。(注意!!!这个可以解答上一个博文的疑问)

2.2 重入

10.6节讨论了可重入函数和信号处理函数,线程在这两个方面也是类似的。

  • 如果一个函数对多个线程来说是可重入的,则这个函数是线程安全的。(但不能说明对信号处理程序来说是可重入的)
  • 线程安全:如果一个函数在同一个时间点被多个线程安全地调用,则此函数是线程安全的。
  • 测试操作系统是否支持线程安全函数的方法:
    • 编译时,测试_POSIX_THREAD_SAFE_FUNCTIONS。
    • 运行时,sysconf函数传入_SC_THREAD_SAFE_FUNCTIONS参数。
  • 造成线程不安全的情况:
    • 函数返回的数据存放在静态的内存缓冲区中。
    • *

2.3 线程私有数据

  • 线程私有数据也叫线程特定数据(thread-specific data),是存储和查询某个特定线程相关数据的一种机制。

3. 相关函数

3.1 线程属性相关

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);
  • 功能:
    • init:初始化一个pthread_attr_t结构。
    • destroy:反初始化pthread_attr_t结构。
  • 参数:
    • attr:指向pthread_attr_t 结构的指针。
  • 注意:
    • 使用pthread_attr_init后,attr指向的结构的内容就是当前操作系统的线程属性的默认值。
    • 如果pthread_attr_inin**的实现**对属性对象的内存空间是动态分配的,则pthread_attr_destroy会释放内存空间。
pthread_attr_getdetachstate 和 pthread_attr_setdetachstate
  • 原型:
       #include <pthread.h>

       int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
       int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
  • 功能:
    • setdetachstate:设置pthread_attr_t结构的detachstate属性为PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE。
    • getdetachstate:获取detachstate属性。(从attr到detachstate)
  • 参数:
    • attr:指向pthread_attr_t结构的指针。
    • detachstate: 指向保存分离状态的指针变量。(获取到的状态存到此变量指向的内存中)
pthread_attr_getstack 和 pthread_attr_setstack
  • 原型:
       #include <pthread.h>

       int pthread_attr_setstack(pthread_attr_t *attr,
                                 void *stackaddr, size_t stacksize);
       int pthread_attr_getstack(const pthread_attr_t *attr,
                                 void **stackaddr, size_t *stacksize);
  • 功能:设置/获取线程栈属性。
  • 参数:
    • attr:指向pthread_attr_t结构的指针。
    • stackaddr:栈的最低内存地址,但并不一定是栈的开始位置。
    • stacksize:栈大小。
  • 返回值:成功0, 否则错误编号。
pthread_attr_getstacksize 和 pthread_attr_setstacksize
  • 原型:
       #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);
  • 功能:设置/获取线程属性stacksize。
  • 参数:略。
  • 返回:成功0,否则错误编号。
pthread_attr_getguardsize 和 pthread_attr_setguardsize
  • 原型:
       #include <pthread.h>

       int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
       int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
  • 功能:设置/获取警戒区域大小。(用以避免栈溢出,通常值为系统页大小)
  • 返回:成功0, 否则错误编号。
  • 注意:
    • 当修改线程属性stackaddr时,会使警戒区无效,即guardsize==0。
    • 如果guardsize线程属性被修改了,操作系统可能会把它取为页大小的整数倍。

3.2 同步属性

  • 这个知识点不大理解。p345-p348
pthread_mutexattr_*(互斥量属性)
  • 原型:
       #include <pthread.h>

       int pthread_mutexattr_init(pthread_mutexattr_t *attr);
       int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

       int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
       int pthread_mutexattr_gettype(const  pthread_mutexattr_t  *attr,  int *kind);

       int pthread_mutexattr_getpshared(const pthread_mutexattr_t
           *restrict attr, int *restrict pshared);
       int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
           int pshared);

       int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict
           attr, int *restrict robust);
       int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
           int robust);
  • 功能:
    • init: 初始化。
    • destroy: 反初始化。
    • getpshared:获取进程共享属性。
    • setpshared:设置进程共享属性。
    • getrobust:获取健壮的互斥量属性的值。
    • setrobust:设置健壮的互斥量属性的值。
    • gettype:获取互斥量类型属性。
    • settype:设置互斥量类型属性。
      • 类型互斥量属性控制这互斥量的锁定特性。
      • 互斥量类型行为:
互斥量没有解锁时重新加锁不占有时解锁已解锁时解锁
PTHREAD_MUTEX_NORMAL死锁未定义未定义
PTHREAD_MUTEX_ERRORCHECK返回错误返回错误返回错误
PTHREAD_MUTEX_RECURSIVE允许返回错误返回错误
PTHREAD_MUTEX_DEFAULT未定义未定义未定义

* 不占有时解锁:一个线程解锁被另一个线程线程加锁的互斥量。

  • 返回:成功0, 否则错误编号。
pthread_rwlockattr_*(读写锁属性)
  • 原型:
       #include <pthread.h>
       int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
       int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

       int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t
           *restrict attr, int *restrict pshared);
       int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
           int pshared);
  • 功能:
    • getpshared:获取进程共享属性。(读写锁唯一支持的属性)
    • setpshared:设置进程共享属性。
  • 参数:略。
  • 返回值:成功0, 否则错误编号。
pthread_condattr_(条件变量属性)
  • Single UNIX Specification目前定义了条件变量的两个属性:进程共享属性时钟属性
  • 原型:
       #include <pthread.h>

       int pthread_condattr_init(pthread_condattr_t *attr);
       int pthread_condattr_destroy(pthread_condattr_t *attr);

       int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
       int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

       int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
       int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
  • 功能:
    • getclock:获取可被用于pthread_cond_timedwait函数的时钟ID。(注意在使用pthread_cond_timedwait前需要用pthread_condattr_t对象对条件变量进程初始化)
    • setclock:对时钟ID进行修改。
  • 参数:
    • clock_id:时钟ID。
  • 返回:成功0, 否则错误编号。
pthread_barrierattr_*(屏障属性)
  • 原型:
       #include <pthread.h>

       int pthread_barrierattr_init(pthread_barrierattr_t *attr);
       int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);

       int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
       int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
  • 功能:略。
  • 参数:略。
  • 返回:成功0,否则错误编号。

3.3 重入

lockfile相关
  • 原型:
       void flockfile(FILE *filehandle);
       int ftrylockfile(FILE *filehandle);
       void funlockfile(FILE *filehandle);
  • 功能:锁定标准io的FILE。(注意这个锁是递归的,也就是可以多次锁定的)
  • 参数:略
  • 返回:
    • ftrylockfile:成功0,若不能获取锁,返回非0值。
    • 其他两个没有返回值。
unlocked的字符操作

为了处理每读写一个字符就获取锁和释放锁一次的开销,可以使用下面函数。(p356)
* 原型:(其实有好多这种函数,只列出书上列出的几个,其他man手册有)

       #include <stdio.h>

       int getc_unlocked(FILE *stream);
       int getchar_unlocked(void);
       int putc_unlocked(int c, FILE *stream);
       int putchar_unlocked(int c);
  • 功能:略。
  • 参数:略。
  • 返回:略。

3.4 线程私有数据

在分配线程特定数据之前,需要创建与该数据关联的

pthread_key相关
  • 原型:
       #include <pthread.h>

       int  pthread_key_create(pthread_key_t  *key,  void  (*destr_function)(void *));
       int pthread_key_delete(pthread_key_t key);
       int pthread_setspecific(pthread_key_t key, const void *pointer);
       void * pthread_getspecific(pthread_key_t key);
  • 功能:(p359和p360的两个创建键的方法要再学习)
    • create:创建一个键,还未与线程私有数据值关联。(这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有(特定)数据地址关联。
    • delete:取消键与线程私有(特定)数据值的关联关系。
    • setspecific:把键和线程特定数据关联起来。
    • getspecific:获取线程私有数据的地址。
  • 参数:
    • key:指向键的指针。(这是一个值-结果参数)
    • destructor:析构函数,没有则设为NULL。(pthread_exit, 线程执行返回, 正常退出是会调用,exit/_exit/_Exit/abort不会调用,线程取消时,只有在最后的清理处理程序执行返回后,析构函数才会执行)
    • pointer:指向需要关联的私有数据地址的指针。
  • 返回:0或错误编号。

3.5 取消选项

  • 有两个线程属性并没有包含在pthread_attr_t结构中:可取消状态可取消类型
  • 这两个属性影响这线程在相应pthread_cancel函数调用时所呈现的行为。
  • 可取消状态有:PTHREAD_CANCEL_DISABLE 和 PTHREAD_CANCEL_ENABLE。
  • 可取消类型有:PTHREADCANEL_DEFERRED 和 PTHREAD_CANCEL_ASYNCHRONOUS。
  • 推迟取消:在遇到取消点才能取消。(取消点在p362)
  • 异步取消:可以在任意时候取消。
pthread_setcancelstate和pthread_setcanceltype
  • 原型:
       #include <pthread.h>

       int pthread_setcancelstate(int state, int *oldstate);
       int pthread_setcanceltype(int type, int *oldtype);
  • 功能:
    • pthread_setcancelstate:修改可取消状态。
    • pthread_setcanceltype:修改可取消类型。
  • 参数:略。
  • 返回:0或错误编号。
pthread_testcancel
  • 原型:
       #include <pthread.h>

       void pthread_testcancel(void);
  • 功能:添加自己的取消点。

3.8 线程和信号

  • 每个线程都有自己的信号屏蔽字(但会继承父线程的),但信号的处理是进程中所有线程共享的。
    • 意味着:某线程修改某信号的处理行为后,所有线程都共享这个处理行为的改变。
  • 进程用sigprocmask函数来阻止信号发送。
  • 线程用pthread_sigmask。
  • 为避免错误,线程在调用sigwait**之前**,必须阻塞那些它正在等待的信号。(返回前恢复)
  • 把信号发送给进程用:kill。
  • 把信号发送给线程用:pthread_kill。
  • 闹钟定时器是进程资源,并且所有的线程共享相同的闹钟。
  • 如果一个信号的动作是终止进程,把此信号传递给某个线程仍然会杀死整个进程。
pthread_sigmask
  • 原型:
       #include <signal.h>

       int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
  • 功能:
    • 设置屏蔽信号集(当set不为NULL时)。
    • 获取屏蔽信号集(当set为NULL且oldset不为NULL时)。
  • 参数:
    • how:
      • SIG_BLOCK:把信号机添加到线程信号屏蔽中。
      • SIG_SETMASK:用信号集替换线程的信号屏蔽
      • SIG_UNBLOCK:从线程信号屏蔽中移除信号集。
    • set:目标操作信号屏蔽集。
    • oldset:原来的信号屏蔽集。
sigwait
  • 原型:
       #include <signal.h>

        int sigwait(const sigset_t *set, int *sig);
  • 功能:等待一个或多个信号出现。
  • 参数:
    • set:指定线程等待的信号集。
    • sig:返回时,sig指向的整数将包含发送信号的数量。(???)
  • 返回:0或错误编号。
pthread_kill
  • 原型:
       #include <signal.h>

       int pthread_kill(pthread_t thread, int sig);
  • 功能:
    • 发送信号给线程。
    • sig==0时,检查线程是否存在。

3.9 线程和fork

  • fork后,子进程会继承互斥量,读写锁,条件变量等。(具体见博客:待补)
  • 如果父进程有一个以上线程,fork后,如果子进程接着不exec,就要清理锁状态。(子进程内部只有一个线程)
  • 可多次调用pthread_atfork函数从而设置多套fork处理函数(疑问:多套如何工作?)。
  • 使用多套时,处理程序的调用顺序是不同的:(这样可以允许多个模块注册它们自己的fork处理函数,而且可以保持锁的层次)
    • parent和child 是以它们注册时的顺序进行调用。
    • prepare的调用顺序则和它们注册时相反。
pthread_atfork
  • 原型:
       #include <pthread.h>

       int  pthread_atfork(void (*prepare)(void), void (*parent)(void), void(*child)(void));
  • 功能:通过此函数建立fork处理函数清除锁状态。(最多可安装3个清理锁的函数)
  • 参数:
    • prepare:由父进程fork创建子进程前调用,任务是获取父进程定义的所有锁。
    • parent:fork创建子进程之后、返回之前在父进程上下文中调用,任务是对prepare中获取的所有锁进行解锁。
    • child:在fork返回前在子进程上下文中调用,任务和parent一样。
  • 注意:不会出现加锁一次解锁两次的情况,prepare获取的锁在fork之后就有了副本。

4. 拓展知识

  • 对进程来说,虚地址空间的大小是固定的。

4.1 pthread_once

  • 原型:
       #include <pthread.h>

       pthread_once_t once_control = PTHREAD_ONCE_INIT;
       int  pthread_once(pthread_once_t  *once_control, void (*init_routine)(void));
  • 功能:只进程一次初始化。
  • 参数:
    • once_contral:必须是一个非本地变量(如全局变量或静态变量)而且必须初始化为PTHREAD_ONCE_INIT。

5. 疑问

  • 什么时进程共享属性?
    • 控制着条件变量时可以被进程的国歌线程使用,还是可以被多个进程的线程使用。
    • 值可为:PTHREAD_PROCESS_SHARED(多进程中的多线程可用) 和 PTHREAD_PROCESS_PRIVATE(初始化该屏障的进程内可以)。
    • 还要再深入。
  • *

6. 习题

12.1 在linux系统中运行图12-17,把输出结果重定向到文件,解释结果。

  • 终端输出和重定向结果:
    这里写图片描述
  • 这是一个行缓冲和全缓冲的问题。
  • 标准输出定向到终端时,是行缓冲,每次打印一行。
  • 标准输出定向到文件时,是全缓冲,fork的时候,复制了缓冲区内容,故而有此结果(当缓冲区满或关闭文件的时候,冲洗缓冲区)

12.5 假设可以在一个程序中创建多个线程执行不同的任务,为什么还是可能会需要fork?

  • 可能需要在程序中运行另一个程序(exec)
  • 可能单个进程受到某些限制(如默认单进程最多能打开1024个文件)

7. 参考资料

  • 《unix环境高级编程》第12章

8. 相关下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值