条件变量、线程池以及线程的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

相关推荐
小O_好好学6 分钟前
CentOS 7文件系统
linux·运维·centos
新晓·故知19 分钟前
<基于递归实现线索二叉树的构造及遍历算法探讨>
数据结构·经验分享·笔记·算法·链表
从0至130 分钟前
力扣刷题 | 两数之和
c语言·开发语言
魔理沙偷走了BUG31 分钟前
【数学分析笔记】第4章第4节 复合函数求导法则及其应用(3)
笔记·数学分析
x晕x39 分钟前
Linux dlsym符号查找疑惑分析
linux·运维·服务器
小比卡丘1 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
一个不知名程序员www1 小时前
leetcode第189题:轮转数组(C语言版)
c语言·leetcode
冷白白1 小时前
【C++】C++对象初探及友元
c语言·开发语言·c++·算法
活跃的煤矿打工人1 小时前
【星海saul随笔】Ubuntu基础知识
linux·运维·ubuntu
z樾1 小时前
Github界面学习
学习