[APUE chapter 11] POSIX线程

简介

线程有学习了使用,以及一些基础的知识,现在再细致地学习一下线程。因此做此记录。
主要学习:

  • 线程相关概念
  • 线程标识
  • 线程创建
  • 线程终止
  • 线程同步

线程的基础概念及知识

概念

  • 绝对时间:把当前时间加上目标时长。(一般用timespec结构表示)
  • 每个线程都包含表示执行环境所需的信息:
    • 线程ID
    • 一组寄存器
    • 调度优先级和策略
    • 信号屏蔽字
    • errno变量(见AUPE 1.7节)
    • 线程私有数据(见APUE 12.6节)
  • 编译链接的时候需使用 -lpthead
  • POSIX线程的功能测试宏是 _POSIX_THREADS,可以用#ifdef进行测试,从而编译时确定是否支持POSIX线程。
  • 也可以用_SC_THREADS常数用于调用sysconf函数,在运行时确定是否支持线程。
  • 线程ID用pthread_t类型来表示,通常是一个结构, 故
    • 比较两个线程ID的时候需要用一个函数来进行比较。也可使用整数,但是可移植性不好(进程用pid_t表示,是一个非负整数)
    • 但是使用结构不能可移植地打印线程ID。
  • 新建线程继承线程的浮点环境信号屏蔽字,但是新建线程的挂起信号集合会被清除。
  • 每个线程提供一个errno的副本。(是为了和其他使用errno的函数兼容)
  • 如果进程中的任意线程调用了exit、_Exit 或者 _exit, 那么整个进程都会终止。
  • 线程的终止方式:
    • 从调用例程中返回,返回值是线程的退出码。
    • 被同一进程中的其他线程取消。
    • 线程调用pthread_exit()。
  • 线程可以安排它退出时需要调用的函数,与进程用atexit的类似。见下面pthread_cleanup相关函数。
  • 进程和线程原语的比较:

    进程原语 线程原语 描述
    fork pthread_create 创建新的控制流
    exit pthread_exit 从现有的控制流中退出
    waitpid pthread_join 从控制流中得到退出状态
    atexit pthread_cancel_push 注册在退出控制流时调用的函数
    getpid pthread_self 获取控制流的ID
    abort pthread_cancel 请求控制流的非正常退出
  • 默认情况下, 线程的终止状态会保存直到对该线程调用pthread_join。

  • 当线程处于分离状态时,线程的底层存储资源可以在线程终止时立即被回收, 故而如果对该线程调用pthread_join()会产生未定义行为。
  • 什么时候要进行线程同步:
    • 当一个线程可以修改变量,而其他线程也可以读取或修改的时候。

线程同步

互斥量(mutex)

  • 互斥变量用pthread_mutex_t类型表示。在使用互斥变量之前,必须先对它初始化,可以设置为常量PTHREAD_MUTEX_INITIALIZER(只适合静态分配的互斥量)或通过pthread_mutex_init()函数进行初始化。
  • 如果动态分配互斥量(如通过malloc分配),在释放内存前需要调用pthread_mutex_destroy()。
  • 避免由于两个或多个以上的互斥量的使用造成死锁的方法:可以用pthread_mutex_trylock进行尝试,如果成功,则继续运行,否则释放已经占有的锁,做清理工作,过一段时间后再尝试。

读写锁(rwlock)

  • 读写锁有三种状态:
    • 读模式下加锁状态
    • 写模式下加锁状态
    • 不加锁状态
  • 读写锁中, 一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。
  • 读写锁在写加锁状态时,所有的加锁操作都会阻塞;在读加锁状态时,写加锁操作阻塞,读加锁不阻塞。直到所有读锁都释放,才能再加写锁。(通常实现会在加读锁状态下,如果来了写锁,会阻塞以后的读锁,避免写锁一直得不到响应)

条件变量

  • 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
  • 使用前必须先初始化。

自旋锁(spin lock)

  • 自旋锁和互斥量类似,但不是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。
  • 自旋锁通常作为底层原语用于实现其他类型的锁。
  • 自旋锁适用情况:
    • 锁被持有时间短,且线程不希望在重新调度上花费太多成本。
  • 注意:不要调用在持有自旋锁情况下可能进入休眠状态的函数。(浪费资源)

屏障(barrier)

  • 屏障是用户协调多个线程并行工作的同步机制。
  • 工作模式:等待所有线程到达某一点,然后再一起运行。(pthread_join时一种屏障:一个线程直到等待另一个退出)
  • *

相关函数

pthread_equal()

  • 原型:
       #include 
       int pthread_equal(pthread_t t1, pthread_t t2);
  • 功能:比较两个线程ID是否相等。
  • 返回:不相等返回0, 相等返回非0.

pthread_self()

  • 原型:
       #include 
       pthread_t pthread_self(void);
  • 功能:获取自身线程ID。

pthread_create()

  • 原型:
       #include 
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
  • 功能:新增一个线程。
  • 参数:
    • thread: 一个指向新建线程ID的指针。
    • attr: 一个指针,用于定制新建线程的属性,NULL使用默认线程属性。(APUE 12.3节讨论)
    • start_routine: 一个函数指针,指向新建线程后要运行的函数。
    • arg: 一个指针,指向运行函数的参数。
  • 返回:成功返回0, 否则返回错误编号。

pthread_exit()

  • 原型:
       #include 
       void pthread_exit(void *retval);
  • 功能:退出线程
  • 参数:
    • retval: 线程的返回值。可以是一个结构,可被pthread_join获取

pthread_join()

  • 原型:
       #include 
       int pthread_join(pthread_t thread, void **retval);
  • 功能:等待指定线程终止、获取指定线程的返回值。
    • 调用线程一直阻塞,直到指定线程调用pthread_exit或从启动例程返回或被取消。
    • 如果线程简单地从启动例程返回,则retval包含返回码;如果是取消,retval指定的内存单元被设置为PTHREAD_CANCELED.
  • 参数:
    • thread:指定线程的ID;
    • retval:pthread_exit中是单指针,这里是双重指针,获取指定线程的retval.
  • 返回值:成功返回0, 否则返回错误编号。

pthread_cancel()

  • 原型:
       #include 
       int pthread_cancel(pthread_t thread);
  • 功能:取消线程,请求取消同一进程中的线程。(注意:不懂待线程终止,仅仅是提出请求)
  • 参数:
    • thread:指定的线程ID;

pthread_cleanup_push() 和 pthread_cleanup_pop()

  • 原型:
       #include 
       void pthread_cleanup_push(void (*routine)(void *), void *arg);
       void pthread_cleanup_pop(int execute);
  • 功能:设置线程清理处理函数。
  • 参数:
    • routine:函数指针,指向线程清理函数。
    • arg: 无类型指针,线程清理函数的参数。
    • execute:当此参数设置为0时,清理函数不被调用。
  • 注意:
    • 当线程执行以下动作时, 清理函数routine由pthread_cleanup_push函数调度。
      • 当用pthread_exit时;
      • 响应取消请求时;(pthread_cancel())
      • 用非零execute参数调用pthread_cleanup_pop时。
    • 如果启动例程是return的,则不会调用处理函数,用pthread_exit()则会调用。

pthread_detach

  • 原型:
       #include 
       int pthread_detach(pthread_t thread);
  • 功能: 分离线程。
  • 注意:也可以通过修改pthread_create函数的线程属性,创建一个处于分离状态的线程。

pthread_mutex相关

  • 原型:
       #include 

       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
       pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
       pthread_mutex_t   errchkmutex   =   PTHREAD_ERRORCHECK_MUTEX_INITIAL-IZER_NP;

       int  pthread_mutex_init(pthread_mutex_t  *mutex, const pthread_mutex‐
       attr_t *mutexattr);
       int pthread_mutex_lock(pthread_mutex_t *mutex);
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       int pthread_mutex_unlock(pthread_mutex_t *mutex);
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 功能:
    • init: 初始化互斥量。
    • lock: 对互斥量加锁。
    • trylock:尝试对互斥量加锁,如果没有加锁则锁住,返回0,否则返回EBUSY,不会阻塞。
    • unlock: 对互斥量解锁。
    • destroy: 销毁互斥量。
  • 参数:
    • mutex:互斥变量。
    • mutexattr:互斥量属性。(APUE 12.4节讨论)
  • 返回:成功0,否则返回错误编号。

pthread_mutex_timelock

  • 原型:
#include 
#include 

int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *tsptr);
  • 功能:和pthread_mutex_lock基本相同,只是在加锁已加锁的互斥量的时候,只阻塞指定时间,如果期间没有解锁,则返回ETIMEDOUT。
  • 参数:
    • mutex:线程ID
    • tsptr:时间结构变量指针。
  • 返回:成功0, 否则错误编号。

pthread_rwlock相关

  • 原型:
       #include 

       int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
           const pthread_rwlockattr_t *restrict attr);
       int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

       int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
       int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

       int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
       int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

       int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  • 功能:初始化/销毁读写锁。(读写锁在使用前,必须初始化)
  • 参数:
    • rwlock:读写锁。
    • attr:读写锁的属性。
      返回:成功0,否则错误编号。

pthread_rwlock_time*

  • 原型:
       #include 
       #include 

       int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
           const struct timespec *restrict abstime);
       int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
           const struct timespec *restrict abstime);
  • 功能: 带有超时的加锁函数。指定时间内不能获得锁,则返回ETIMEDOUT错误。
  • 返回: 成功0, 否则错误编号。
  • 注意:超时指定的时绝对时间,不是相对时间(互斥量那里也是,有疑问?)

pthread_cond相关

  • 原型:
       #include 

       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       int    pthread_cond_init(pthread_cond_t   *cond,   pthread_condattr_t *cond_attr);
       int pthread_cond_signal(pthread_cond_t *cond);
       int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
       int  pthread_cond_timedwait(pthread_cond_t *cond, 
                   pthread_mutex_t *mutex, const struct timespec *abstime);
       int pthread_cond_destroy(pthread_cond_t *cond);
  • 功能:
    • init:初始化。
    • destroy:反初始化(deinitialize)
    • wait:等待条件变量变为真。
    • timedwait:和wait类似,只是多了一个等待时长。
    • signal:通知一个等待该条件的线程:条件已经满足。
    • broadcase:通知所有等待该条件的线程:条件已经满足。
  • 参数:
    • cond:条件变量;
    • attr:条件变量属性;
    • mutex:互斥锁。
    • abstime:时间结构指针。
      注意:一定要在改变条件状态后再给线程发送信号。

pthread_spin相关

  • 原型:
       #include 

       int pthread_spin_destroy(pthread_spinlock_t *lock);
       int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
       int pthread_spin_lock(pthread_spinlock_t *lock);
       int pthread_spin_trylock(pthread_spinlock_t *lock);
       int pthread_spin_unlock(pthread_spinlock_t *lock);
  • 功能:略。
  • 参数:
    • lock:锁;
    • pshared:进程共享属性,表明自旋锁时如何获取的。如:
      • 设为PTHREAD_PROCESS_SHARED:自旋锁能被访问锁底层内存的线程获取,即便那些线程不属于桶一进程。
      • 设为PTHREAD_PROCESS_PRIVATE:自旋锁只能被初始化该锁的进程内的线程使用。
  • 返回:成功0, 否则错误编号。

pthread_barrier相关

  • 原型:
       int pthread_barrier_destroy(pthread_barrier_t *barrier);
       int pthread_barrier_init(pthread_barrier_t *restrict barrier,
           const pthread_barrierattr_t *restrict attr, unsigned count);
       int pthread_barrier_wait(pthread_barrier_t *barrier);
  • 功能:
    • wait:线程已经完成工作,等待所有其他线程赶上来。(计数,当wait的线程数>=count的时候,所有阻塞线程继续运行)
  • 参数:
    • barrier:屏障变量。
    • attr:屏障属性。
    • count:必须达到屏障的线程数目。(如果要改变计数,需要线程destroy再init)

拓展

  • 获取时间的方法:
struct timespec tout;
struct tm       *tmp;
clock_gettime(CLOCK_REALTIME, &tout);
tmp = localtime(&tout.tv_sec);
strftime(buf, sizeof(buf), "%r", tmp);

参考资料

  • 《unix环境高级编程(第三版)》第11章
  • linux系统的man手册

相关下载

相关库

  • pthread_mutex相关函数在ubuntu16.04中没有man pages,需要安装glibc-doc。
  • pthread_rwlock相关函数在ubuntu16.04中没有man pages,需要安装manpages-posix和manpages-posix-dev
    CSDN:http://download.csdn.net/detail/i_scream_/9668052

示例代码下载

p310: 打印线程ID。
p312: 获取终止线程的退出码。
p313: pthread_exit参数的不正确使用。
p316: 线程清理处理函数
p322: 使用互斥量保护数据结构
p323: 使用两个互斥量。
p325: 简化的锁(未完成)
p330: 使用读写锁。
p334: 使用条件变量。
p337: 使用屏障的一个排序程序(排序8百万数据只要0.5s,在ubuntu 1604 64bit 8G内存 4核 i5下)

CSDN:http://download.csdn.net/detail/i_scream_/9668052
github:https://github.com/isshe/2.Advanced_programming/tree/master/chapter_11

你可能感兴趣的:(【初探】操作系统)