多线程编程学习::POSIX 多线程基础(二)

来源:互联网 发布:excel数组 编辑:程序博客网 时间:2024/05/20 20:47

线程建立与使用

创建线程

  • 通过 pthread_create() 函数创建线程
    • 向该函数传递线程函数地址和线程函数参数
    • 线程函数只有一个 void* 参数
    • 该函数返回 pthread_t 类型的线程ID
  • 一般调用该函数创建线程,然后调用 pthread_join() 函数等待线程结束
    • 在当前线程从函数 pthread_create() 中返回以及新线程被调度执行之间不存在同步关系
    • 新线程可能在当前线程从 pthread_create() 返回值前就运行了
    • 或在当前线程从 pthread_create() 返回之前,新线程就可能已经运行完毕了
pthread_join()
  • 阻塞其调用者直到指定线程终止,然后可以选择地保存线程的返回值
  • 当 pthread_join() 调用返回时,被连接线程就已经被分离(detached),再也不能连接该线程了
  • 如果连接(joining)线程不关心返回值,或者它知道被连接(joined)的线程根本不返回任何值,则可向 pthread_join() 的 &retval 参数传递 NULL,此时,被连接线程的返回值将被忽略

初始线程

  • C 程序运行时,首先运行 main() 函数,main() 函数所在线程称为初始线程或主线程
  • 初始线程可调用 pthread_self() 获得其 ID,也可调用 pthread_exit() 来终止自己
  • 从 main() 返回将导致进程终止,也将使进程内所用线程终止
  • 在 main() 中调用 pthread_exit(),这样进程就必须等待所有线程结束后才能终止
  • 若初始线程将其 ID 保存在一个其他线程可以访问的空间,则其他线程就可以等待初始线程的终止或者分离初始线程

线程分离

  • 分离一个正在运行的线程不会对线程带来任何影响,仅仅是通知系统当该线程结束时,其所属资源可以被回收
  • 分离线程意味着通知系统不再需要此线程,允许系统将分配给它的资源回收
  • 一个没有被分离的线程终止时会保留其虚拟内存,包括堆栈和其他系统资源

线程生命周期

在任意时刻,线程处于下表的四个基本状态之一。

状态说明就绪 ready线程能够运行,但在等待可用的处理器
  • 可能刚刚启动
  • 或刚刚从阻塞中恢复
  • 或被其他线程抢占
运行 running线程正在运行;在多处理器系统中,可能有多个线程处于运行状态阻塞 blocked线程由于等待处理器外的其他条件无法运行,如条件变量的改变、加锁互斥量或IO操作结束终止 terminated不是被分离,也不是被连接,一旦线程被分离或者连接,它就可以被回收
  • 线程从起始函数中返回
  • 或调用pthread_exit
  • 或被取消,终止自己并完成所有资源清理工作

线程状态转换如下图。

thread

说明

  • 线程开始处于就绪状态
  • 当线程运行时,它调用特定的起始函数
  • 它可能被其他线程抢占,或者因等待外来事情而阻塞自己
  • 最终线程完成工作,或者从起始函数返回,或者调用 pthread_exit 函数,即进入终止状态
  • 如果线程已被分离,则它立刻被回收重用;否则,线程停留在终止状态直到被分离或被连接

就绪态

  • 线程刚被创建时
  • 线程被解除阻塞再次可以运行时
  • 运行线程被抢占时,如时间片到

被阻塞

  • 试图加锁一个已经被锁住的互斥量
  • 等待某个条件变量
  • 调用 singwait 等待信号
  • 执行无法立即完成的 IO 操作
  • 内存页错误之类的系统操作

初始线程(main()函数所在线程)与普通线程区别

  • 初始线程的启动函数 main() 是从程序外部调用的;如 crt0.o 文件复制初始化进程并调用 main() 函数;而普通线程的启动函数及其运行参数均由 pthread_create() 函数创建线程时传入,且由 CPU 调度的
  • main()函数的参数是 argc 和 argv;普通线程的参数是 void*,且由 pthread_create() 函数传入
  • 若普通线程从启动函数中返回,则线程终止,而其他线程依然可以运行;但初始线程从 main() 返回时,进程终止,进程内所有线程也被终止
  • 若希望在初始线程终止时,进程中的其他线程继续执行,则需要在初始线程调中调用 pthread_exit() 而非从 main() 返回
  • 大多数系统,初始线程运行在默认进程堆栈上,该堆栈可以增长到足够尺寸;而某些实现中,普通线程的堆栈空间是受限的
  • 如果线程堆栈溢出,则程序会出现段错误

线程睡眠原因

  • 被阻塞,需要的某个资源不可用
  • 被抢占,即系统将处理器分配给其他线程

pthread_join() 的详细解释

  • 用来等待一个线程的结束;
  • 是一个线程阻塞函数,调用它的函数将一直等待到被等待的线程结束为止
  • 如,主线程调用 pthread_join() 等待它创建的线程运行结束,即主线程调用该函数后会被阻塞
  • 当函数返回时,被等待的线程的资源被回收
  • 若此时新线程没有运行,则它将在主线程被阻塞后从就绪态进入运行态;当新线程运行完毕并返回时,主线程才会被解除阻塞,返回就绪态;当处理器可用时,主线程或立即执行或等到创建的线程终止后重新运行直到结束

线程终止

  • 一般地,线程从启动函数返回来终止自己
  • 当调用 pthread_exit() 退出线程或者调用 pthread_cancel() 取消线程时,线程在调用每个清理过程后也进入终止状态
  • 清理过程又线程通过 pthread_cleanup_push() 注册,且尚未通过 pthread_cleanup_poo() 删除

Linux 系统僵尸线程

  • 如果线程已经被分离,则会被回收;否则,线程处于终止状态,仍然可以被其他线程调用 pthread_join() 连接
  • 这种线程被称为僵尸线程,像 Uni 系统中的进程已经结束但还没有被一个 wait/waitpid 调用回收一样,即使已经死了但还存在
  • 僵尸线程可能会保留其运行时的大部分甚至所有资源,因此不应该让线程长时间处于这种状态;当创建不需要连接的线程时,应该使用 detachstate 属性建立线程使其自动分离

线程回收

  • 如果使用 detachstate 属性(即设置属性为 PTHREAD_CREATE_DETACH )建立线程,或者调用 pthread_detach() 分离线程,则当线程结束时将被立刻回收
  • 如果终止线程没有被分离,则它将一直处于终止状态直到被分离(通过 pthread_detach )或者被连接(通过 pthread_join)
  • 线程一旦被分离,就不能再访问它
  • 回收将释放所有在线程终止时未释放的系统和进程资源,包括
  • 保存线程返回值的内存空间、堆栈
  • 保存寄存器状态的内存空间
  • 实际上线程终止时上述资源就不能被访问了
  • 一旦线程被回收,线程ID就无效了,不能再连接、取消或者执行其他任何操作
  • 终止线程ID可能被分给新线程
原创粉丝点击