条件变量、线程池以及线程的GDB调试学习笔记

目录

一、条件变量

二、线程池概念和实现

三、线程的GDB调试


一、条件变量

应用场景:生产者消费者问题,是线程同步的一种手段。

必要性:为了实现等待某个资源,让线程休眠,提高运行效率

使用步骤

初始化

  • 静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化互斥量

  • 动态初始化

pthread_cond_init(&cond);

生产者线程

  • pthread_mutex_lock(&mutex);
  • 开始生产资源
  • pthread_cond_signal(&cond); //通知一个消费线程

或者

  • pthread_cond_broadcast(&cond); //广播通知多个消费线程
  • pthread_mutex_unlock(&mutex);

消费者线程

  • pthread_mutex_lock(&mutex);

  • while (如果没有资源){ //防止惊群效应

    pthread_cond_wait(&cond, &mutex);

    }

  • 有资源了,消费资源

  • pthread_mutex_unlock(&mutex);

示例代码:

cpp 复制代码
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//初始化条件变量
pthread_cond_t hasTaxi = PTHREAD_COND_INITIALIZER;
//初始化互斥量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

struct taxi{
    struct taxi *next;
    int num;
};

struct taxi *Head = NULL;
void *taxiarv(void *arg)
{
    printf("taxi arrived thread\n");
    pthread_detach(pthread_self());
    struct taxi *tx;
    int i = 1;
    while (1)
    {
        tx = malloc(sizeof(struct taxi));
        tx->num = i;
        printf("taxi %d comming\n",i);
        i++;
        pthread_mutex_lock(&lock);
        tx->next = Head;
        Head = tx;
        pthread_cond_signal(&hasTaxi);//通知消费者车来了
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
    
    pthread_exit(0);
}
void *takeTaxi(void *arg)
{
    printf("take taxi thread\n");
    pthread_detach(pthread_self());
    struct taxi *tx;
    while (1)
    {
        pthread_mutex_lock(&lock);
        while (Head == NULL)//如果没有资源
        {//放置惊群效应
            pthread_cond_wait(&hasTaxi,&lock);
        }
        //有资源了,消费资源
        tx = Head;
        Head = tx->next;
        printf("Take taxi %d\n",tx->num);
        free(tx);
        pthread_mutex_unlock(&lock);
    }
    
    pthread_exit(0);
}
int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,taxiarv,NULL);
    pthread_create(&tid2,NULL,takeTaxi,NULL);
    while (1)
    {
        sleep(1);
    } 
}

运行结果:

注意:

  1. pthread_cond_wait(&cond, &mutex),在没有资源等待是是先unlock 休眠,等资源到了,再lock,所以pthread_cond_wait 和 pthread_mutex_lock 必须配对使用。

pthread_mutex_unlock

如果资源没有来sleep

如果来了

pthread_mutex_lock

  1. 如果pthread_cond_signal或者pthread_cond_broadcast 早于 pthread_cond_wait ,则有可能会丢失信号。

  2. **pthead_cond_broadcast 信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。**即代码中的 while (Head == NULL)//如果没有资源。

二、线程池概念和实现

**概念:**通俗的讲就是一个线程的池子,可以循环的完成任务的一组线程集合

打个比喻:比如一个公司招人做项目,招到一个人,做完项目就把这个人解雇,然后又来项目就再招人再解雇,招聘和解雇流程繁琐消耗大量时间,而线程池呢就相当于招聘到一个人不解雇,来一个项目做完等着,来第二个项目继续做,就剩去了解雇和再招聘的时间。

**必要性:**我们平时创建一个线程,完成某一个任务,等待线程的退出。但当需要创建大量的线程时,假设T1为创建线程时间,T2为线程任务执行 时间,T3为线程销毁时间,当T1+T3>T2,这时候就不划算了,使用线程池可以降低频繁创建和销毁线程所带来的开销,任务处理时间比较短的时候这个好处非常显著。

线程池的基本结构:

  1. 任务队列,存储需要处理的任务,由工作线程来处理这些任务
  2. 线程池工作线程,它是任务队列任务的消费者,等待新任务的信号

线程池的实现

  1. 创建线程池的基本结构:
  • 任务队列链表:typedef struct Task;
  • 线程池结构体:typedef struct ThreadPool;
  1. 线程池的初始化:
  • pool_init()
    {
    创建一个线程池结构
    实现任务队列互斥锁和条件变量的初始化
    创建n个工作线程
    }
  1. 线程池添加任务
  • pool_add_task
    {
    判断是否有空闲的工作线程
    给任务队列添加一个节点
    给工作线程发送信号newtask
    }
  1. 实现工作线程
  • workThread
    {
    while(1)
    {
    等待newtask任务信号
    从任务队列中删除节点
    执行任务
    }
    }

5.线程池的销毁

  • pool_destory
    {
    删除任务队列链表所有节点,释放空间
    删除所有的互斥锁条件变量
    删除线程池,释放空间
    }

示例代码:

cpp 复制代码
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define POOL_NUM 10
//任务队列链表
typedef struct Task{
     void *(*func)(void *arg);//定义函数体指针
     void *arg;//定义参数
     struct Task *next;//因为任务是一个链表,所以还要定义一个指针
}Task;
//线程池结构体
typedef struct ThreadPool{
    pthread_mutex_t taskLock;//任务锁
    pthread_cond_t newTask;//有任务来了通知条件变量,即线程池
    pthread_t tid[POOL_NUM];//定义10个线程
    Task *queue_head;//拿到任务的头部
    int busywork;//表示有几个任务工作
}ThreadPool;
ThreadPool *pool;
//工作线程
void *workThread(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&pool->taskLock);
        pthread_cond_wait(&pool->newTask,&pool->taskLock);//没有任务的时候阻塞
        Task *ptask = pool->queue_head;//取出任务
        pool->queue_head = pool->queue_head->next;//指向任务队列的下一个节点
        pthread_mutex_unlock(&pool->taskLock);
        ptask->func(ptask->arg); //函数真正的运行
        pool->busywork--;
    }
    
}
void *realwork(void *arg)
{
    printf("Finish work %d\n",(int)arg);
}
//向线程池添加任务
void pool_add_task(int arg)
{
    Task *newTask;
    //访问线程池临界资源,所以要加锁
    pthread_mutex_lock(&pool->taskLock);
    while(pool->busywork>=POOL_NUM)
    {
        pthread_mutex_unlock(&pool->taskLock);
        usleep(10000);//休眠的时候要把锁释放掉
        pthread_mutex_lock(&pool->taskLock);
    }
    pthread_mutex_unlock(&pool->taskLock);

    newTask = malloc(sizeof(Task));
    newTask->func = realwork; //函数指针初始化
    newTask->arg = arg;

    pthread_mutex_lock(&pool->taskLock);
    Task *member = pool->queue_head;
    if(member == NULL)
    {
        pool->queue_head = newTask;
    }else
    {
        while (member->next!=NULL)//遍历链表,找到末尾
        {
            member = member->next;
        }
        member->next = newTask;//将newTask放在链表的尾部
        
    }
    pool->busywork++;
    pthread_cond_signal(&pool->newTask);
    
    pthread_mutex_unlock(&pool->taskLock);
}
//线程池的初始化
void pool_init()
{
    pool = malloc(sizeof(ThreadPool));//对线程池分配一个空间
    pthread_mutex_init(&pool->taskLock,NULL);//对任务进程初始化
    pthread_cond_init(&pool->newTask,NULL);//对条件变量进行初始化
    pool->queue_head = NULL;
    pool->busywork = 0; 
    for(int i = 0; i<POOL_NUM; i++)
    {
        pthread_create(&pool->tid[i],NULL,workThread,NULL);
    }
}
void pool_destory()
{
    Task *head;
    while (pool->queue_head!=NULL)
    {
        head = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        free(head);
    }
    pthread_mutex_destroy(&pool->taskLock);
    pthread_cond_destroy(&pool->newTask);
    free(pool);
}
int main()
{
    pool_init();
    sleep(1);
    for(int i=1;i<=20;i++)
    {
        pool_add_task(i);
    }
    sleep(5);
    pool_destory();
}

运行结果:

执行20个任务,而线程池的容量是10,所以会有10个在等待着执行。

三、线程的GDB调试

示例代码:

cpp 复制代码
#include <pthread.h>
#include <stdio.h>

void *testThread(void *arg)
{
    char *threadName = (char *)arg;
    printf("Current running %s\n",threadName);

    printf("aaaaaa\n");
    printf("bbbbbb\n");
    pthread_exit(0);
}
int main()
{
    pthread_t tid1,tid2;

    pthread_create(&tid1,NULL,testThread,"thread1");
    pthread_create(&tid2,NULL,testThread,"thread2");

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
}

正常运行结果:

显示线程:info thread

切换线程:thread id

将断点打在第6行

GDB设置线程锁:

------set scheduler-locking on/off

GDB为特定线程设置断点

------break location thread id

相关推荐
蒙奇D索大15 分钟前
【11408学习记录】考研数学攻坚:行列式本质、性质与计算全突破
笔记·学习·线性代数·考研·机器学习·改行学it
moxiaoran575323 分钟前
uni-app学习笔记二十四--showLoading和showModal的用法
笔记·学习·uni-app
tcoding35 分钟前
《基于Apache Flink的流处理》笔记
笔记·flink·apache
DartistCode35 分钟前
动手学深度学习pytorch(第一版)学习笔记汇总
pytorch·深度学习·学习
汤姆和佩琦38 分钟前
LLMs基础学习(八)强化学习专题(1)
深度学习·学习·强化学习·马尔可夫决策过程
倔强的石头1061 小时前
【Linux指南】用户与系统基础操作
linux·运维·服务器
云上艺旅1 小时前
centos升级内核
linux·运维·centos
kaikaile19951 小时前
centos开启samba服务
linux·运维·centos
云上艺旅1 小时前
centos部署k8s v1.33版本
linux·云原生·kubernetes·centos
好多知识都想学1 小时前
Centos 7 服务器部署多网站
linux·服务器·centos