本文已参与「新人创作礼」活动,一起开启掘金创作之路。
个人博客:www.cgsx.online/
多线程编程
线程
线程是比进程更小的单位,是完成一个独立任务的完整执行序列,是一个可调度的实体。
线程分为内核线程和用户线程:
- 内核线程运行在内核空间,由内核调度。
- 用户线程运行在用户空间,由线程库调度。
线程有三种实现方式:
-
完全在用户空间实现
即多线程只在用户空间实现,由线程库管理,内核只看得见一个进程,看不见用户空间的多个线程。这些执行线程共享该进程的时间片。
**优点**就是创建和调度进程不需要内核的干预,速度非常快,且不占用额外的内存资源,但**缺点**是对于多处理系统,多个进程无法运行在不同的CPU上。
0. 完全由内核调度
与完全在用户空间实现相反,调度任务交给内核,运行在用户空间的线程库无须管理任务。
0. 双层调度
前两种方式的混合体,结合了前两种方式的优点:1)不会消耗过多的内核资源;2)切换速度快;3)充分利用多处理器的优势。
为什么要使用线程?
多进程模型的缺陷:
- 创建多进程带来更大的开销
- 数据交换需要IPC技术
- 上下文切换耗时
多线程模型优点:
- 上下文切换不需要切换数据区和堆区
- 利用数据区和堆区交换数据
一个进程有数据区,堆区和栈区,一个进程可以包含多个线程,其中多个线程共享数据区,堆区。
线程创建与运行
线程具有单独的执行流,需要单独定义线程的main函数,还要请求操作系统在单独的执行流中执行该函数。
#include <pthread.h>
int pthread_create(
pthread_t* restrict thread, const pthread_attr_t* restrict attr,
void* (*strat_routine)(void*), void* restrict arg
);
- thread 保存新创建线程ID的变量地址值
- attr 传递线程属性的参数,传递NULL表示默认属性
- start_routine 线程main函数的函数指针,执行的函数地址值
- arg 调用函数的传递参数变量地址值
使用线程时需要注意执行流,当进程终止时,线程没有执行完也会一起终止。
所以,当线程在执行时,一定要保证进程不能在线程执行完毕前终止。
我们可以使用pthread_join,使得调用该函数的进程进入等待状态,等待线程执行完毕。
#include<pthread.h>
int pthread_join(pthread_t thread, void** status);
- thread 参数ID的线程终止后,从该函数返回
- status 线程的main函数返回值的指针变量地址值
线程同步问题
多个线程访问同一变量,需要进行同步,使得最后的结果正确。
同步主要解决两个问题:
- 同时访问同一内存空间时发生的情况。
- 需要指定访问同一内存空间的线程执行顺序的情况。
互斥量
互斥量是一种锁机制,为了保护临界区。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexatr_t * attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
/**
* mutex 保存互斥量的变量地址值
* attr 即将创建的互斥量的属性
**/
创建互斥量后可以通过开锁解锁操作对临界区进行保护,加锁后只允许有一个线程对其进行操作。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
信号量
信号量与互斥量的作用一样,但不同的是,互斥量只有有锁与没锁两种状态,即0和1,和信号量是一个整数值,可以设置竞争资源的多少。
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
/**
* sem 信号量变量地址
* pshared 信号量属性,0为同一进程内部使用的信号量
* value 信号量初始值
**/
创建信号量后,通过两个函数对信号量进行增1减1
#include <semaphore.h>
int sem_post(sem_t *sem); // +1
int sem_wait(sem_t *sem); // -1
int sem_trywait(sem_t *sem); // -1,非阻塞版本
条件变量
条件变量提供了一种线程间的通知机制,当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
相关函数如下
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr); // 创建
int pthread_con_destroy(pthread_cond_t* cond); // 销毁
int pthread_cond_broadcast(pthread_cond_t* cond); // 唤醒所有等待目标条件变量的线程
int pthread_cond_signal(pthread_con_t* cond); // 唤醒一个等待目标条件变量的线程
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex); // 等待目标条件
/**
* cond 指向要操作的目标变量
* cond_attr 指定条件变量的属性
* mutex 互斥锁,保证pthread_cond_wait的原子性
**/
调用wait,会先把线程放入条件变量的等待队列,然后将互斥锁mutex解锁。要加锁的目的是防止在wait过程中signal与broadcast会修改条件变量,导致线程一直被挂起。