4 多线程

理论基础

  • 线程也是为了实现多任务并发执行,能实现多个阻塞任务同时执行
  • 线程(LWP)是粒度更小的任务执行单元
  • 进程是资源分配的基本单位,线程是任务器进行任务调度的最小单位
  • 一个进程可以有多个线程,同一个进程中的多个线程共享进程的资源
  • 线程的切换开销较小
  • 由于多个线程使用同一个进程的资源,会导致每个进程使用资源时产生资源抢占问题,没有多进程安全
  • 每个进程至少有一个线程:主线程
  • 主线程结束后,整个进程也就结束了
  • 多个线程执行没有先后顺序,按时间片轮询上下文切换的方式进行

多线程实现

C库没有提供多线程相关操作,对多线程编程需要依赖第三方库<pthread.h>,编译时加上-lpthread选项链接库

创建线程

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
    • 参数:
      • 用于接收线程号的变量的地址
      • 确定线程属性的结构体的首地址,一般传nullptr表示默认属性
      • 回调函数,是一个函数指针,作为线程体的执行函数,自定义
      • 参数3的参数,没有参数传nullptr
    • 返回值:成功返回0,失败返回一个错误码(线程库中定义)

获取线程号

  • pthread_t pthread_self(void):获取当前线程的线程号
    • 返回值:调用线程的线程号,不会失败

退出线程

  • void pthread_exit(void *retval);:退出当前线程
    • 参数:退出时的状态,一般传nullptr

回收线程资源

  • int pthread_join(pthread_t thread, void **retval);:阻塞回收线程资源
    • 参数:
      • 要回收的线程的线程号
      • 线程退出时的状态,一般传nullptr
    • 返回值:成功返回0,失败返回一个错误码(线程库中定义)
  • int pthread_detach(pthread_t thread);:将指定线程设置为分离态,分离态的线程退出后资源由系统自动回收,非阻塞
    • 参数:要设置为分离态的线程
    • 返回值:成功返回0,失败返回一个错误码(线程库中定义)

线程同步互斥机制

  • 同一个进程的多个线程共享进程的资源,被共享的资源为临界资源
  • 多个线程对公共资源的抢占问题,访问临界资源的代码段为临界区
  • 多个线程抢占公共资源的现象为竞态
  • 同步互斥机制解决资源抢占问题

互斥锁

  • 互斥锁的本质也是一个特殊的临界资源,该临界资源被某个线程拥有后其他线程不能拥有该资源,直到该资源被线程释放
  • 一个互斥锁同一时刻只能被一个线程拥有

API:

  • pthread_mutex_t mutex;:定义一个互斥锁变量,即创建了一个互斥锁
  • 初始化互斥锁:
    • int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);:动态初始化
      • 参数:
        • 互斥锁的地址
        • 互斥锁的属性,一般传nullptr
      • 返回值:成功返回0,失败返回错误码(线程库中定义)
    • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;:静态初始化
  • int pthread_mutex_lock(pthread_mutex_t *mutex);:获取资源,若互斥锁已经被其他线程锁定,会阻塞
    • 参数:互斥锁的地址
    • 返回值:成功返回0,失败返回错误码(线程库中定义)
  • int pthread_mutex_unlock(pthread_mutex_t *mutex);:释放资源
    • 参数:互斥锁的地址
    • 返回值:成功返回0,失败返回错误码(线程库中定义)
  • int pthread_mutex_destroy(pthread_mutex_t *mutex);:销毁互斥锁
    • 参数:互斥锁的地址
    • 返回值:成功返回0,失败返回错误码(线程库中定义)
#include <myHead.h>

using namespace std;

int num = 120;    //临界资源
pthread_mutex_t mutex;    //创建互斥锁

void* task1(void* arg) {
    while (true) {
        sleep(1);
        //临界区
        pthread_mutex_lock(&mutex);
        num -= 10;
        printf("task1 -10 : num = %d\n", num);
        pthread_mutex_unlock(&mutex);
    }
}

void* task2(void* arg) {
    while (true) {
        sleep(1);
        pthread_mutex_lock(&mutex);
        num += 10;
        printf("task2 +10 : num = %d\n", num);
        pthread_mutex_unlock(&mutex);
    }
}

int main(int argc, char* argv[]) {
    pthread_mutex_init(&mutex, nullptr);    //初始化互斥锁
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
        fputs("tid1 creaete error\n", stdout);
        return -1;
    }
    if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
        fputs("tid2 creaete error\n", stdout);
        return -1;
    }
    pthread_mutex_destroy(&mutex);    //销毁互斥锁
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    return 0;
}

无名信号量

  • 线程同步:多个线程执行有先后顺序,不会出现抢占临界资源的现象
  • 同步机制常用于生产者消费者模型:消费者线程要想执行必须先执行生产者线程,多个任务有顺序执行
  • 本质上也是一个特殊的临界资源,内部维护一个整数,某个线程要执行前先申请信号量的资源
    • 若信号量为0,阻塞等待
    • 若信号量为正数,线程获得资源,信号量减少

API:

头文件:<semaphore.h>

  • sem_t sem;:定义一个无名信号量类型,即创建了一个无名信号量
  • int sem_init(sem_t *sem, int pshared, unsigned int value);:初始化无名信号量
    • 参数:
      • 无名信号量的地址
      • 判断进程还是线程间的同步
        • 0:线程间的同步
        • 非零:进程间的同步,需要在共享内存段中使用
      • 无名信号量的初始值
    • 返回值:成功返回0,失败返回-1并置位错误码
  • int sem_wait(sem_t *sem);:阻塞申请无名信号量的资源,成功后无名信号量--(P操作)
    • 参数:无名信号量地址
    • 返回值:成功返回0,失败返回-1并置位错误码
  • int sem_post(sem_t *sem);:释放无名信号量资源,成功后无名信号量++(V操作)
    • 参数:无名信号量地址
    • 返回值:成功返回0,失败返回-1并置位错误码
  • int sem_destroy(sem_t *sem);:销毁无名信号量
    • 参数:无名信号量地址
    • 返回值:成功返回0,失败返回-1并置位错误码
#include <myHead.h>

using namespace std;

sem_t sem;    //创建无名信号量

//生产者线程
void* task1(void* arg) {
    int k = 5;
    while (k--) {
        sleep(1);
        fputs("生产了一张5090\n", stdout);
        sem_post(&sem);
    }
    pthread_exit(nullptr);
}

//消费者线程
void* task2(void* arg) {
    int k = 5;
    while (k--) {
        sem_wait(&sem);
        fputs("买了一张5090\n", stdout);
    }
    pthread_exit(nullptr);
}

int main(int argc, char* argv[]) {
    sem_init(&sem, 0, 0);    //初始化无名信号量
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
        fputs("tid1 creaete error\n", stdout);
        return -1;
    }
    if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
        fputs("tid2 creaete error\n", stdout);
        return -1;
    }
    sem_destroy(&sem);    //销毁无名信号量
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    return 0;
}

条件变量

  • 条件变量本质也是一个临界资源,维护了一个队列,当消费者线程要执行时,先进入队列等待生产者的唤醒,生产者执行完后唤醒消费者执行;实现了一个生产者线程和多个消费者线程的同步关系
  • 多个消费者在进入休眠队列的过程是互斥的,需要使用互斥锁实现互斥操作

API:

  • pthread_cond_t cond;:定义一个条件变量,即创建了一个条件变量
  • 初始化条件变量:
    • int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);:动态初始化条件变量
      • 参数:
        • 条件变量的地址
        • 条件变量的属性,一般传nullptr
      • 返回值:成功返回0,失败返回错误码(线程库中定义)
    • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;:静态初始化条件变量
  • int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);:将线程放入休眠队列,等待其他线程唤醒
    • 参数:
      • 条件变量的地址
      • 互斥锁地址,为解决多个消费者进程进入休眠队列时产生的竞态问题,需要使用互斥锁
    • 返回值:成功返回0,失败返回错误码(线程库中定义)
  • 唤醒消费者线程:
    • int pthread_cond_broadcast(pthread_cond_t *cond);:唤醒休眠队列中的所有消费者线程
      • 参数:条件变量的地址
      • 返回值:成功返回0,失败返回错误码(线程库中定义)
    • int pthread_cond_signal(pthread_cond_t *cond);:唤醒休眠队列中的队首消费者线程
      • 参数:条件变量的地址
      • 返回值:成功返回0,失败返回错误码(线程库中定义)
  • int pthread_cond_destroy(pthread_cond_t *cond);:销毁条件变量
    • 参数:条件变量地址
    • 返回值:成功返回0,失败返回-1并置位错误码
#include <myHead.h>

using namespace std;

pthread_cond_t cond;    //创建条件变量
pthread_mutex_t mutex;    //创建互斥锁

//生产者任务
void* task1(void* arg) {
    for (int i = 1;i <= 3;i++) {
        sleep(1);
        fputs("生产了一张5090\n", stdout);
        pthread_cond_signal(&cond);
    }
    // sleep(1);
    // fputs("生产了三张5090\n", stdout);
    // pthread_cond_broadcast(&cond);
    pthread_exit(nullptr);
}

//消费者任务
void* task2(void* arg) {
    pthread_mutex_lock(&mutex);    //上锁

    pthread_cond_wait(&cond, &mutex);    //进入休眠队列
    fputs("买了一张5090\n", stdout);

    pthread_mutex_unlock(&mutex);    //解锁
    pthread_exit(nullptr);
}

int main(int argc, char* argv[]) {
    pthread_cond_init(&cond, nullptr);    //初始化条件变量
    pthread_mutex_init(&mutex, nullptr);    //初始化互斥锁
    pthread_t tid1, tid2, tid3, tid4;
    if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
        fputs("tid1 creaete error\n", stdout);
        return -1;
    }
    if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
        fputs("tid2 creaete error\n", stdout);
        return -1;
    }
    if (pthread_create(&tid3, nullptr, task2, nullptr) != 0) {
        fputs("tid2 creaete error\n", stdout);
        return -1;
    }
    if (pthread_create(&tid4, nullptr, task2, nullptr) != 0) {
        fputs("tid2 creaete error\n", stdout);
        return -1;
    }

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    pthread_join(tid4, nullptr);
    pthread_cond_destroy(&cond);    //销毁条件变量
    pthread_mutex_destroy(&mutex);    //销毁互斥锁
    return 0;
}

wait原理

pthread_mutex_lock(&mutex);    //外部上锁
while(资源==nullptr){
	pthread_cond_wait(&cond,&mutex);    //内部有一次解锁加锁操作
}

//C++11实现:
//cond.wait(lock,[](){return 资源!=nullptr});

//访问临界资源

pthread_mutex_unlock(&mutex);    //外部解锁锁p
  1. 外部加锁
  2. 将线程放在等待队列后,内部解锁,以免其他线程无法访问临界资源
  3. 等待唤醒后阻塞竞争锁,竞争到锁后,内部加锁
  4. wait返回后条件不一定满足
  5. 访问临界资源
  6. 外部解锁

C++11中的多线程

支持线程创建,互斥锁,条件变量
头文件<thread>,封装了C风格的线程API,编译需要加-lpthread

线程相关操作

  • 创建线程

    • 通过构造函数创建一个线程类对象,构造函数参数为一个线程体函数,若线程体函数有参数,需要在传入的线程体函数后面再把参数传进去
    • 传入的线程体函数不限返回值和参数类型,函数可以是任意类型:全局函数,类成员函数,仿函数,Lambda表达式
    #include <myHead.h>
    
    using namespace std;
    
    class Myclass {
    public:
        void memberFunct(int num) {
            cout << "num=" << num << endl;
        }
    };
    
    int main(int argc, char* argv[]) {
        Myclass a;
        thread th1(&Myclass::memberFunct, &a, 12);
        th1.join();
        return 0;
    }
    
  • this_thread::get_id():获取线程号

  • 回收线程资源

    • th.join():阻塞回收,线程只能回收一次,使用joinable()函数判断是否可以回收
    • th.detach():非阻塞回收

互斥锁

头文件<mutex>

使用std::unique_lock,std::lock_guard更安全

成员函数:

  • 构造函数创建互斥锁
  • lock():上锁
  • unlock():解锁
  • 析构函数销毁互斥锁

条件变量

头文件<condition_variable>

成员函数:

  • 构造函数创建条件变量
  • wait(lock):将消费者线程放入休眠队列中
    • 参数:
      • 用于保护条件变量的互斥锁
      • 可以加一个参数pred,谓词,相当于唤醒的附加条件
  • notify_one():唤醒一个进程
  • notify_all():唤醒所有进程
  • 析构函数销毁条件变量
#include <myHead.h>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;

condition_variable con;
mutex mu;

void task1() {
    for (int i = 1;i <= 5;i++) {
        sleep(1);
        cout << "生产一辆BYD" << endl;
        con.notify_one();
    }
}

void task2() {
    unique_lock<mutex> lock(mu);
    con.wait(lock);
    cout << "买一辆BYD" << endl;
    mu.unlock();
}

int main(int argc, char* argv[]) {
    thread th1(task1);
    for (int i = 1;i <= 5;i++) {
        thread th2(task2);
        th2.detach();
    }
    th1.join();
    return 0;
}
posted @ 2026-10-07 16:16  mofei1116  阅读(4)  评论(0)    收藏  举报