12.线程创建回收取消清理

  1. 进程和线程区别

    1. 进程有独立的地址空间
    2. Linux为每个进程创建task_struct
    3. 每个进程都参与内核调度,互不影响
    4. 进程在切换时系统开销大
    5. 很多操作系统引入了轻量级进程LWP
    6. 同一进程中线程共享相同地址空间
    7. Linux不区分进程、线程
  2. 线程特点

    1. 通常线程指的是共享相同地址空间的多个任务
    2. 使用多线程的好处
      1. 大大提高了任务切换的效率
      2. 避免了额外的TLB&cache的刷新
  3. 线程共享资源(公有资源)

    1. 一个进程中的多个线程共享以下资源:
      1. 可执行的命令
      2. 静态数据
      3. 进程中打开的文件描述符
      4. 当前工作目录
      5. 用户ID
      6. 用户组ID
  4. 线程私有资源

    1. 每个线程私有的资源包括:
      1. 线程ID(TID)
      2. PC(程序计数器)和相关寄存器
      3. 堆栈
      4. 错误号(errno)
      5. 优先级
      6. 执行状态和属性
  5. linux线程库

    1. pthread线程库提供了如下基本操作
      1. 创建线程
      2. 回收线程
      3. 结束线程
    2. 同步和互斥机制
      1. 信号量
      2. 互斥锁
  6. 线程创建pthread_create

    1. #include <pthread.h>
    2. int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void*) *arg);
    3. 成功返回0,失败返回错误码
    4. thread线程对象
    5. attr线程属性,NULL代表默认属性
    6. routine线程执行的函数
    7. arg传递给toutine的参数,参数是void*,注意传递参数格式
    8. pthread_t pthread_self(void) 查看自己的TID
  7. 线程运行特点:

    1. 主进程的退出,他创建的线程也会退出。
    2. 线程创建需要时间,如果主进程马上退出,那线程不能得到执行
  8. 线程结束-pthread_exit

    1. #include <pthread.h>
    2. void pthread_exit(void *retval);
      1. 结束当前线程
      2. retval可被其他线程通过pthread_join获取
      3. 线程私有资源被释放
  9. 获取线程ID

    1. 通过pthread_create函数的第一个参数,通过在线程里面调用pthread_self()
    2. tid获取
  10. 线程的参数传递

    1. 通过地址传递参数,注意类型转换
        1. void*类型指针不能直接用*取值,因为编译不知道数据类型。解决方法:转换为指定的指针类型后在用*取值 
    2. 通过值传递,这时候编译器会告警,需要程序员自己保证数据长度正确
  11. 线程的回收-pthread_join

    1. #include<pthread.h>
    2. int pthread_join(pthread_t thread, void **retval);
      1. 对于一个默认属性的线程A来说,线程占用的资源并不会因为执行结束而得到释放
      2. 成功返回0,失败返回错误码
      3. thread要回收的线程对象
      4. 调用线程阻塞到thread结束
      5. *retval接收线程thread的返回值
      6. 注意:pthread_join是阻塞函数,如果回收的线程没有结束,他就会一直等待
  12. 线程分离两种方式实现回收

    1. int pthread_detach(pthread_t thread);
      1. 成功:0, 失败:错误号
      2. 指定该状态,线程主动与主空线程断开关系。线程结束后(不会产生僵尸线程)
    2. pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
      1. 设置线程属性为分离
        1. pthread_attr_init(&attr);
        2. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  13. 线程回收内存演示

    1. 通过进程占用内存可以看出来线程回收和不回收的区别
  14. 线程的取消和清理

    1. int pthread_cancel(pthread_t thread);杀死一个线程
      1. 注意:线程的取消要有取消点才可以,不是说取消就取消,取消点是阻塞的系统调用
      2. 运行段错误:可以使用gdb调试
      3. 如果没有取消点,手动设置一个
        1. void pthread_testcancel(void);
    2.  int pthread_setcancelstate(int state, int * oldstate);
      1. state传参数

        1. PTHREAD_CANCEL_ENABLE
        2. PTHREAD_CANCEL_DISABLE
    3. int pthread_setcanceltype(int type, int *oldtype);//设置取消类型
      1. PTHREAD_CANCEL_DEFERRED//等到取消点才取消,延时取消
      2. PTHREAD_CANCEL_ASYNCHROUNOUS//目标线程会立即取消
  15. 线程的清理

    1. void pthread_cleanup_push(void (*routine)(void*),void *arg)
      1. 第一个参数是函数指针,返回值void
      2. 第二个参数是函数参数
      3. routine函数被执行的条件:
        1. 被pthread_cancel取消掉
        2. 执行pthread_exit
        3. 非0参数执行pthread_cleanup_pop()
    2. void pthread_cleanup_pop(int execute)
      1. 两个函数必须成对使用,只写一个编译都不通过,即使pthread_cleanup_pop不会被执行到也必须写上。
      2. pthread_cleanup_pop()被执行且参数为0,pthread cleanup_push回调函数routine不会被执行
      3. pthread_cleanup_push 和pthread_clean_pop可以写成多对,routine执行顺序正好相反
      4. 线程内的return可以结束线程但是不能触发pthread_cleanup_push里面的回调函数
  16. 线程的同步和互斥

    1. 临界资源
      1. 一次只允许一个任务(进程、线程)访问的共享资源
      2. 比如写文件,只能由一个线程写,同时写会写乱
      3. 比如外设打印机打印的时候只能由一个程序使用
      4. 外设基本上都是不能共享的资源。
      5. 生活中必入卫生间,同一时间只能由一个人使用。
      6. 必要性:临界资源不可以共享
    2. 临界区
      1. 访问临界资源的代码
    3. 互斥机制
      1. mutex互斥锁
      2. 任务访问临界资源前申请锁,访问完后释放锁
    4. man手册找不到pthread_mutex_xxxxxx出现No manual entry for pthread_mutex_xxx解决方式:
      1. apt-get install manpages-posix-dev
    5. 互斥锁的创建和销毁

      1. 两种方法创建互斥锁,静态方式和动态方式
      2. 动态方式:
        1. 初始化pthread_mutex_init
          1. #include<pthread.h>
          2. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr)
            1. 成功时返回0,错误返回错误码
            2. mutex执行要初始化的互斥锁对象
            3. attr用于指定互斥锁属性,NULL表示缺省属性
      3. 静态方式:
        1. pthread_mutext_t mutex = PTHREAD_MUTEX_INITIALIZER;
      4. 锁的销毁:
        1. int pthread_mutex_destroy(pthread_mutext_t *mutex)
        2. 在Linux中,互斥锁不占用任何资源,因此LinuxThreads中的pthread_mutext_destroy()除了检查锁状态以外(锁定状态返回EBUSY)没有其他动作。
      5. 互斥锁的使用:
        1. int pthread_mutex_lock(pthread_mutex_t *mutex)申请锁
        2. int pthread_mutex_unlock(pthread_mutex_t *mutex)释放锁
        3. int pthread_mutex_trylock(pthread_mutex_t *mutex)
        4. 成功返回0,失败返回错误码
        5. mutex指向要初始化的互斥锁对象
        6. pthread_mutex_lock如果无法获得锁,任务阻塞
        7. pthread_mutex_trylock如果无法获得锁,返回EBUSY而不是挂起等待
        8. 如果有多个临界资源的话,需要定义多个锁
      6. vim设置全文格式化:gg=G
    6. 读写锁
      1. 必要性:提高线程执行效率
      2. 特性:
        1. 写者:使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁,否则写者将等待,直到没有读者和写者
        2. 读者:读者使用读锁,如果当前没有写者,读者立即获得读锁,否则读者等待,直到没有写者
        3. 注意:同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。读写锁处于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁清秋会被阻塞,以避免写者长时间的不写锁。
          1. 初始化一个读写锁pthread_rwlock_init
          2. 读锁定读写锁pthread_rwlock_rdlock
          3. 非阻塞读锁定pthread_rwlock_tryrdlock
          4. 写锁定读写锁pthread_rwlock_wrlock
          5. 非阻塞写锁定pthread_rwlock_trywrlock
          6. 解锁读写锁pthread_rwlock_unlock
          7. 释放读写锁pthread_rwlock_destroy
    7. 死锁

      1. 概念:
      2. 死锁只有多把锁的时候出现,一把锁不会,互相获取对方的资源
      3. 避免方法:
        1. 锁越少越好,最好使用一把锁
        2. 如果一把锁不够,一定要调整好锁的顺序
    8. 条件变量
      1. 应用场景:
        1. 生产者消费者问题
        2. 必要性:为了实现等待某个资源,让线程休眠,提高运行效率
      2. pthread_cond_wait(&m_cond, &m_mutex);// 等待资源
      3. int pthread_cond_timedwait(pthread_cond_t * restrict cond,pthread_mutex_t *restrict mutex, const struct timespec * restrict abstime); // 等待超时退出
      4. int pthread_cond_signal(pthread_cond_t *cond);// 信号
      5. int pthread_cond_broadcast(pthread_cond_t *cond);//广播
      6. 使用步骤:
        1. pthread_cond_t cond = PTHREAD_COND_INITIALIZER// 初始化条件变量
        2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER//初始化互斥量
        3. 生产资源线程:
          1. pthread_mutex_lock(&mutex);//加锁
        4. 开始生产资源:
          1. pthread_cond_sigal(&cond)://通知一个消费线程,单播
          2. 或者
          3. pthread_cond_broadcast(&cond);//广播通知多个消费线程
          4. pthread_mutex_unlock(&mutex);
        5. 消费者线程:
          1. pthread_mutex_lock(&mutex);
          2. while(如果没有资源){//防止惊群效应
            1. pthread_cond_wait(&cond, &mutex);//等待资源
          3. }
          4. 有资源了,消费资源
            1. pthread_mutex_unlock(&mutex);
        6. 注意:

          1. pthread_cond_wait(&cond,&mutex)在没有资源等待是先unlock休眠,等资源到了,再lock,所以pthread_cond_wait 和pthread_mutex_lock必须配对使用
          2. 如果pthread_cond_signal或者pthread_cond_broadcast早于pthread_cond_wait,可能会导致信号丢失
      7. pthread_cond_broadcast信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。

      8. #include<pthread.h>
        #include<stdio.h>
        #include<unistd.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 = 0;
            while(1){
                tx = malloc(sizeof(struct taxi));
                tx->num = i++;
                printf("taxi %d comming\n", tx->num);
                pthread_mutex_lock(&lock);
                
                tx->next = Head;
                Head = tx;
                //pthread_cond_signal(&hasTaxi);// 生产者释放信号告诉消费者有资源了
                pthread_cond_broadcast(&hasTaxi);
                pthread_mutex_unlock(&lock);
                sleep(1); // 每秒生产一辆车
        
            }
            pthread_exit(0);
        }
        void *taketax(void *arg){// 消费者线程
            printf("take taxi thread\n");
            pthread_detach(pthread_self());
            struct taxi *tx;
            while(1){
                pthread_mutex_lock(&lock);
                while(Head==NULL){// 没有资源的时候等待,防止下面崩溃,必须加这while Head==NULL
                    pthread_cond_wait(&hasTaxi, &lock);// 1.条件变量 2.锁   收到信号解锁
                }
                tx = Head; // 消费资源
                Head=tx->next;
                printf("%d, Take taxi %d\n", (int)arg, tx->num);
                free(tx);
                pthread_mutex_unlock(&lock);
        
            }
            pthread_exit(0);
        
        }
        int main(){
            pthread_t tid1, tid2, tid3, tid4;
            pthread_create(&tid1, NULL, taxiarv, NULL);
            sleep(5);
            pthread_create(&tid2, NULL, taketax, (void*)1);
            pthread_create(&tid3, NULL, taketax, (void*)2);
            pthread_create(&tid4, NULL, taketax, (void*)3);
            while(1){
                sleep(1);
            }
        }
  17. 线程池

    1. 概念:通俗的讲就是一个线程的池子,可以循环的完成任务的一组线程集合
    2. 必要性:
      1. 我们平时创建一个线程,完成某一个任务,等待现成的退出。但当需要创建大量的线程时,假设T1为创建线程时间,T2为线程任务执行事件,T3为线程销毁时间,当T1+T3>T2,这时候就不划算了,使用线程池可以降低频繁创建和销毁线程所带来的开销,任务处理时间比较短的时候这个好处非常显著。
    3. 线程池的基本结构:

      1. 任务队列,需要处理的任务,由工作线程来处理这些任务
      2. 线程池工作线程,他是任务队列任务的消费者,等待新任务的信号
      3. 线程池的实现:

        1. 创建线程池的基本结构:

          1. 任务队列链表

            1. typedef struct Task

          2. 线程池结构体

            1. typedef struct ThreadPool

        2. 线程池的初始化

          1. pool_init(){

            1. 创建一个线程池结构

            2. 实现任务队列互斥锁和条件变量的初始化

            3. 创建n个工作线程

          2. }

        3. 线程池添加任务

          1. pool_add_task{

            1. 判断是否有空闲的工作线程

            2. 给任务队列添加一个节点

            3. 给工作线程发送信号newtask

          2. }

        4. 实现工作线程

          1. workThread{

            1. while(1){
              1. 等待newtask任务信号
              2. 从任务队列中删除节点
              3. 执行任务
            2. }
          2. }

        5. 线程池的销毁

          1. pool_destory{

            1. 删除任务队列链表所有节点,释放空间

            2. 删除所有的互斥锁条件变量

            3. 删除线程池,释放空间

          2. }

    4. #include<pthread.h>
      #include<stdio.h>
      #include<unistd.h>
      #include<stdlib.h>
      
      #define POOL_NUM 10
      typedef struct Task{
          void *(*func)(void *arg);
          void *arg;
          struct Task *next;
      };
      
      typedef struct ThreadPool{
          pthread_mutex_t taskLock;//互斥量
          pthread_cond_t newTask; // 条件变量
          pthread_t tid[POOL_NUM]; // 线程数组
          struct 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);
              struct 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){
          struct 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(struct Task));
          newTask->func = realwork;//函数指针的使用
          newTask->arg = (void*)arg;
          pthread_mutex_lock(&pool->taskLock);
          struct Task *member = pool->queue_head;
          if(member==NULL){
              pool->queue_head = newTask;
          }else{
              while(member->next!=NULL){
                  member=member->next;
              }
              member->next = newTask;//遍历到尾部加入新任务
      
          }
          pool->busywork++;
          pthread_cond_signal(&pool->newTask);
          pthread_mutex_unlock(&pool->taskLock);
      }
      
      
      void pool_init(){
          pool = malloc(sizeof(struct 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, i);
          }
      }
      
      void pool_destory()
      {
          struct Task *head;
          while(pool->queue_head!=NULL){
              head = pool->queue_head;
              pool->queue_head =pool->queue_head->next;
              free(pool->queue_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();
          return 0;
      }

  18. 线程的gdb调试

    1. 显示线程info thread
    2. 切换线程
      1. thread xxx
      2. bt
    3. GDB为特定线程设置断点
      1. break location thread id 
    4. GDB设置线程锁
      1. set scheduler-locing on/off
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值