• 所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。
  • 相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享“4G”内存空间,使得系统资源消耗减少。
  • 查看线程:

image.png

  • 对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程,而对于线程而言,也有一个“类似于进程的PID号”,名为tid,其本质是一个pthread_t类型的变量。
  • 对于线程号而言,其仅仅在其所属的进程上下文中才有意义
  • 获取线程号:pthread_t pthread_self(void);
  • 线程创建:int pthread_create(pthread_t thread, const pthread_attr_t attr,void (start_routine) (void ), void arg);
    • 第一个参数为pthread_t指针,用来保存新建线程的线程号
    • 第二个参数表示了线程的属性,一般传入NULL表示默认属性
    • 第三个参数是一个函数指针,就是线程执行的函数。这个函数返回值为void,形参为void
    • 第四个参数则表示为向线程处理函数传入的参数,若不传入,可用NULL填充
  • 当主线程伴随进程结束时,所创建出来的线程也会立即结束,不会继续执行。
  • 创建出来的线程的执行顺序是随机竞争的,并不能保证哪一个线程会先运行
  • 对于线程的参数传递,在处理实际项目中,往往会遇到传递多个参数的问题,我们可以通过结构体来进行传递。
  • 线程退出
    • 线程的退出情况有三种:第一种是进程结束,进程中所有的线程也会随之结束。第二种是通过函数pthread_exit来主动的退出线程。第三种被其他线程调用pthread_cancel来被动退出。
    • 线程主动退出:void pthread_exit(void *retval);
    • 线程被动退出:int pthread_cancel(pthread_t thread);
  • 线程资源回收
    • 当线程结束后,主线程可以通过函数pthread_join/pthread_tryjoin_np来回收线程的资源,并且获得线程结束后需要返回的数据。
    • 线程资源回收(阻塞方式):int pthread_join(pthread_t thread, void **retval);
      • 默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据
    • 线程资源回收(非阻塞方式):int pthread_tryjoin_np(pthread_t thread, void **retval);
      • 通过返回值判断是否回收掉线程,成功回收则返回0;参数与pthread_join一致

线程控制

  • 当线程在运行过程中,去操作公共资源,如全局变量的时候,可能会发生彼此“矛盾”现象;为了解决对临界资源的竞争问题,pthread线程引出了互斥锁来解决临界资源访问。通过对临界资源加锁来保护资源只被单个线程操作,待操作结束后解锁,其余线程才可获得操作权。
  • 互斥锁(互斥量)mutex
    • 多个线程都要访问某个临界资源,比如某个全局变量时,需要互斥地访问:我访问时,你不能访问。
    • 初始化互斥量:int pthread_mutex_init(phtread_mutex_t mutex, const pthread_mutexattr_t restrict attr);
      • 初始化一个互斥量,第一个参数是改互斥量指针,第二个参数为控制互斥量的属性,一般为NULL
      • 也可以调用宏来快速初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
    • 互斥量加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);
    • 互斥量解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);
    • 当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
    • 当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象
    • 非阻塞方式加锁:int pthread_mutex_trylock(pthread_mutex_t *mutex); 非阻塞模式通过返回值来判断是否加锁成功
    • 互斥量销毁:int pthread_mutex_destory(pthread_mutex_t *mutex); 销毁互斥量的,传入互斥量的指针,就可以完成互斥量的销毁,成功返回0
  • 解决了临界资源的访问,但似乎对线程的执行顺序无法得到控制,因线程都是无序执行;于是引入了信号量的概念,解决线程执行顺序
  • 信号量跟互斥量不一样,互斥量用来防止多个线程同时访问某个临界资源。
    • 初始化信号量:int sem_init(sem_t *sem,int pshared,unsigned int value);
      • 第一个参数传入sem_t类型指针;
      • 第二个参数传入0代表线程控制,否则为进程控制;
      • 第三个参数表示信号量的初始值,0代表阻塞,1代表运行
    • 信号量P/V操作:int sem_wait(sem_t sem); int sem_post(sem_t sem);
    • sem_wait函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行“sem-1”的操作
    • sem_post函数会释放指定信号量的资源,执行“sem+1”操作
    • 信号量申请(非阻塞方式):int sem_trywait(sem_t *sem); 功能与sem_wait一致,唯一区别在于此函数为非阻塞
    • 信号量销毁:int sem_destory(sem_t *sem);
  • 条件变量是一种同步机制,用来通知其他线程条件满足了。一般是用来通知对方共享数据的状态信息,因此条件变量是结合互斥量来使用的
    • 初始化条件变量
      • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
      • int pthread_cond_init(pthread_cond_t cond, pthread_condattr_t cond_attr);//cond_attr通常为NULL
    • 销毁条件变量:int pthread_cond_destroy(pthread_cond_t *cond);
    • 等待条件变量:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
      • 需要结合互斥量来使用
      • image.png
    • 通知条件变量:int pthread_cond_signal(pthread_cond_t *cond); 只会唤醒一个等待cond条件变量的线程
  • 线程使用:
    • image.png
  • 互斥量使用:
    • image.png
  • 信号量使用
    • image.png

POSIX

  • 可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。
  • 一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程。这点很重要,因为应用程序使用的这种编程接口实际上并不需要和内核 提供的系统调用对应。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系 统调用也不存在问题。实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异
  • 如下所示,当应用程序调用printf()函数时,printf函数会调用C库中的printf,继而调用C库中的write,C库最后调用内核的write()。image.png
  • Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供
  • 从程序员的角度看,系统调用无关紧要;他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道;库函数及应用程序是怎么使用系统调用不是内核所关心的。
  • 所有的C程序都可以使用C库,而由于C语言本身的特点,其他语言也可以很方便地把它们封装起来使用。此外,C库提供了POSIX的 绝大部分API。

    linux把fork函数封装成posix_fork(随便说的),windows把creatprocess函数也封装成posix_fork,都声明在unistd.h里。这样,程序员编写普通应用时候,只用包含unistd.h,调用posix_fork函数,程序就在源代码级别可移植了。