• Pthread线程库

    Linux的API

    对象操作LINUX Pthread API
    线程创建
    退出
    等待
    pthread_create
    pthread_exit
    pthread_join
    互斥锁创建
    销毁
    加锁
    解锁
    pthread_mutex_init
    pthread_mutex_destroy
    pthread_mutex_lock
    pthread_mutex_unlock
    条件变量创建
    销毁
    触发
    广播
    等待
    pthread_cond_init
    pthread_cond_destroy
    pthread_cond_signal
    pthread_cond_broadcast
    pthread_cond_wait
    读写锁创建
    等待
    销毁
    pthread_rwlock_init
    pthread_rwlock_rdlock
    pthread_rwlock_destroy
    • 线程的实现:windows、Linux
    • Pthread库:POSIX标准中的thread API
    • Glibc与LinuxThread
    • Glibc和NPTL
    • Sgetconf GUN_LIBPTHREAD_VERSION

使用pthread库

  • 安装man手册
    • apt install glibc-doc manpages-posix-dev
  • 程序的编译
    • gcc main.c -lpthread
    • /usr/lib/libpthread.a
  • pthread常用API

在目录下查找文件

find -name libpthread.a

创建一个线程

API接口说明

函数原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

-函数功能:创建一个线程

-参数说明:

-thread:指向线程ID的指针

-attr:线程属性

-start_routine:线程执行实体入口

-arg:传递给线程的参数

-typedef unsigned long int pthread

当pthread_create成功返回时,新创建线程的线程ID会被设置成thread所指向的内存单元

attr参数用于制定各种不同的线程属性

新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递一个以上参数,需要把这些参数放在一个结构体中。

线程的终止

  • 线程终止的三种方式
    • 从start_routine正常return
    • 显示调用pthread_exit
      • 函数原型:void pthread_exit(void *retval);
      • 返回值通过参数retval传递
    • 线程被pthread_cancel取消

单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。

  1. 线程可以简单地从启动例程中返回,返回值是线程的退出码。
  2. 线程可以被同一个进程中的其他线程pthread_cancel取消。
  3. 线程调用pthread_exit。

线程pthread_exit与exit的区别

  • 如果进程中的任意线程调用了exit、_Exit或者_exit,终止整个进程。
  • 线程调用pthread_exit,只会结束当前线程,不影响程序的执行。

等待线程的终止

线程分两种

-Joinable

  • PTHREAD_CREATE_JOINABLE
  • 可通过pthread_join等待线程终止
  • 调用pthread_join的线程会阻塞
  • 一个Joinbale线程结束时,资源不会自动释放给系统(堆栈、exit状态等)
  • 当线程终止时,pthread_join会回收该线程资源,然后返回
  • 无pthread_join参与“擦屁股”工作,该线程将变成僵尸线程

-Unjoinable

  • PTHREAD_CREATE_DETACHED
  • 可通过pthread_detach分离一个线程
  • int pthread_detach(pthread_t thread);
  • 线程终止时,资源会自动释放给系统

API接口

  • 函数原型:int pthread_detach(pthread_t thread);
  • 函数功能:将指定线程与当前线程分离
  • 参数说明:指定要分离的线程ID

线程的属性

默认参数

  • 调度参数:
  • 线程栈地址:
  • 线程栈大小:8M
  • 栈未尾警戒缓冲区大小:PAGESIZE
  • 线程的分离状态:joinable、detached
  • 继承性:PTHREAD_INHERIT_SCHED、PTHREAD_EXPLICIT_SCHED
  • 作用域:PTHREAD_SCOPE_PROCESS、PTHREAD_SCOPE_SYSTEM
  • 调度策略:SCHED_FIFO、SCHED_RR、SCHED_OTHER

相关API函数

  • int pthread_attr_init(pthread_attr_t *attr);
  • int pthread_attr_destroy(pthread_attr_t *attr);
  • int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
  • int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
  • int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
  • int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
  • int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

线程调度与运行

-核心级线程

  • 由内核调度,有利于并发使用多处理器资源

线程安全

多线程下的资源访问

其次,多个线程的任务之间还可能存在顺序依赖的关系,一个线程未能完成某些操作之前,其他线程不能或不应该运行。

多个线程之间需要同步。

想象如下场景:你和你的朋友合租一套公寓,这套公寓只有1个卫生间。当你朋友正在使用卫生间的时候,你就无法使用了。多线程也会遇到类似的问题。

多个线程生活在进程地址空间这同一个屋檐下,若存在多个线程操作共享资源,则需要同步,否则可能会出现结果错误、数据结构遭到破坏甚至是程序崩溃等后果。

因此多线程编程中存在临界区的概念,临界区的代码只允许一个线程执行,线程提供了锁机制来保护临界区。

当其他线程来到临界区却无法申请到锁时,就可能陷入阻塞,不再处于可执行状态,线程可能不得不让出CPU资源。如果设计不合理,临界区非常多,线程之间的竞争异常激烈,频繁地上下文切换也会导致性能急剧恶化。

对于多线程编程,还存在四大陷阱,一不小心就可能落入陷阱之中。

多线程的四个陷阱

分别是:

  • 死锁(Dead Lock)
  • 饿死(Starvation)
  • 活锁(Live Lock)
  • 竞态条件(Race Condition)

客观地说,多线程编程的难度要更大一些,需要程序员更加小心,更加谨慎。

当你需要使用多线程的时候,一定要花费足够的时间小心地规划每个线程的分工,尽可能地减少线程之间的依赖。

良好的设计,合理的分工是多线程编程至关重要的环节。

若初期随意地设计线程的分工,那么在最后,你很有可能不得不花费大量的时间来优化性能,定位bug,甚至不得不推倒重来。

资源划分

——共享的资源

  • 代码段、数据段、地址空间
  • 打开的文件、信号处理程序

——独占的资源

  • 程序计数器:PC
  • 寄存器
  • 栈空间
    • 不同体系不同分配方式
    • 用户线程和管理线程栈是分离的

——进程与线程资源

  • 一室一厅一卫
  • 三室一厅一卫
内核空间
线程1的栈空间
线程2的栈空间
线程3的栈空间
数据
代码

线程安全

  • 对共享资源的安全访问

    • 临界区与临界资源
    • 关中断
    • 锁、条件变量、读写锁
  • 函数引入

    • 可重入函数
    • 线程安全函数

多线程下的资源访问

—— 全局变量

—— 缓冲区(运用场合视频编解码)

——三室一厅卫生间

互斥锁

相关API函数

#include <pthread.h>

pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_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);

条件变量

基本概念

  • 互斥锁缺陷:
    • 不断加锁解锁、查询满足条件、开销很大
    • 加锁开销:用户态-内核态-用户态,阻塞在内核态
    • 解锁开销:用户态-内核态-用户态,唤醒等待线程
  • 条件变量
    • 互斥锁(mutex)搭配使用,允许线程阻塞,等待条件满足的信号
  • 优势:
    • 将互斥锁和条件变量绑定
    • 省去了不断加锁解锁的开销
    • 可以使用广播(broadcast)唤醒所有绑定到该条件变量的线程

#include <pthread.h>

   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);

线程同步:广播

唤醒所有线程

线程同步:读写锁

基本概念

  • 互斥锁:同一时刻只允许一个线程读写
  • 读写锁
    • 允许多个读线程同时读
    • 只允许一个线程写,写的时候会阻塞其他线程(包括读线程)
    • 写优先级高于读

应用场景:读线程多过写线程

相关API:

#include <pthread.h>

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

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

线程池的概念

线程的开销

  • 系统调用的开销:线程的创建、销毁
  • 上下文切换、互斥锁等加锁解锁

线程池原理

  • 预先在池中创建一些线程
  • 无任务时,线程阻塞在池中
  • 有任务时,将任务分配到指定的线程执行
  • 池中线程的数目甚至可根据任务多少动态删减

实现原理

-管理线程

  • 创建并管理线程
  • 任务分配运行

-工作线程

  • 线程池中实际执行任务的线程

-任务接口

  • 每个任务的实现接口

实际上,线程池有异步、缓冲、FIFO

超线程技术

基本概念

  • 电脑 ----打印机
  • 电脑1----打印机
  • 电脑2----打印机

超线程技术

  • Hyper-Threading,简称HT
  • 使用特殊指令将一个物理处理器视为2个逻辑处理器
  • 每个逻辑处理器都可以分配一个线程运行
  • 最大限度地提升CPU资源利用率

实现原理

-交替工作

-共享单元

  • 执行单元
  • 缓存
  • 总线

-应用场所

  • 服务器
  • 工作站

协程的概念

-线程的开销

  • 上下文切换开销
  • 互斥锁的开销

-协程

  • 对共享资源的访问由程序本身维护控制
  • 不需要锁,无调度成本,执行效率高
  • 适合彼此熟悉的程序组件:合作式多任务、管道
  • 进程+线程 -> 进程 + 协程
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐