多线程

多线程    ****
   

1.线程概念:什么是线程,与进程的关系
         进程就是一个运行中的程序,在操作系统中,一个程序运行起来,程序被加载到内存中,
     操作系统创建一个进程描述符(进程控制块)PCB 对程序进行描述控制,因此进程就是pcb,
     在Linux下就是task_struct结构体
         Linux线程用进程pcb模拟,因此Linux的线程是一个轻量级进程,如果说pcb是线程,那么进程
     就是线程组,一个进程中至少包含一个或多个线程
     
         因为cpu调度的是pcb,因此线程是进程内部的一个执行流--线程是cpu调度的基本单位
     因为进程是线程组-->进程是资源分配的基本单位
     
     线程共享:
         进程中所有线程共用一个虚拟地址空间,因此 共享进程 的代码段,数据段
     因为线程数据的共享,因此线程之间进行通信将会变简单
         文件描述符表
         每种信号的处理方式(SIG_IGN、SIG_DFL或自定义信号处理函数)
         当前工作目录
         用户id和组id
        线程独有:
         因为每个线程都是pcb,是进程调度的基本单位,因此线程可以同时运行,但是不会造成调用栈
     混乱,主要是因为每个线程都有自己的数据
              栈  
              寄存器 (上下文数据)  
              信号屏蔽字 
              errno
              线程标识符
        多进程可以并行处理多任务,多进程也可以处理多任务--优缺点分析
             多线程优点:因为多线程共享虚拟地址空间
                    进程间通信简单
                    线程创建/销毁成本更低
                    线程调度成本更低
                    线程的执行粒度更细
             多线程缺点:
                    线程缺乏访问控制---exit 退出的是整个进程
                    健壮性较低---线程的某些错误会导致整个进程退出
        IO密集型程序:大量的磁盘IO操作程序(频繁进行读写)
        cpu密集型程序:大量的数据运算        
        适用场景: shell例子  处理客户端请求时,创建子进程来完成,而父进程只是接收请求
     
     2.线程控制:线程创建,线程终止,线程等待,线程分离
             (1)线程创建:
                     操作系统没有提供直接创建线程的系统调用接口(用户实现一个
                进程的创建非常麻烦),因此实现了一套线程控制接口--封装了 线程库
                提供用户使用,因此我们说创建的线程是一个用户态线程,但是在内核
                对应有一个轻量级进程实现程序的调度运行
                 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
                    //thread : 用于保存返回的线程ID
                    //attr : 线程属性,通常置空
                    //start_routine: 线程入口函数
                    //arg: 给线程传入的参数
                    //返回值: 0  失败:erron
                 pthread_t tid = pthread_self();
                    //返回调用线程的线程id
                //exit(0); //exit退出的是整个进程
                void pthread_exit(void * retval);
                pthread_exit();//退出的是线程
                // 退出调用线程,并且返回retval
                // 线程退出,不仅可以判断终止场景,并且可以获取任务处理结果
                
                ps aux -L   查看线程
                线程ID:
                   task_struct->pid    task_struct->tgid           tid
                       LWP                    PID            线程地址空间首地址                   
             (2)线程终止:
                    pthread_exit();//退出的是线程
                    // 退出调用线程,并且返回retval
                    // 线程退出,不仅可以判断终止场景,并且可以获取任务处理结果
                在main函数中return会退出进程
                在线程入口函数中return只会退出调用线程
                    int pthread_cancel(pthread_t thread);
                    //取消指定线程---让指定线程退出
                    //  -1   PTHREAD_CANCELED
             (3)线程等待:
                     等待指定线程退出--获取指定线程返回值,回收线程资源
                     线程退出也会形成僵尸线程
                     线程创建出来之后,默认有一个属性--joinable属性--线程必须被等待
                因为线程退出后不会自动回收资源,造成内存泄漏
                    int pthread_join(pthread_t thread,void**retval);
                    // thread: 等待线程的ID
                    //retval: 用户获取线程的退出返回值
                    char* retval;
                    pthread_join(tid,(void**)&retval);
             (4)线程分离:
                     分离一个线程(可在任意线程任意时间位置调用,包括分离自己),将线
              程的joinable属性修改为detach,处于这个属性的线程,退出后将会自
              动回收资源,处于这个属性的线程,不能被等待,否则报错
              int pthread_detach(pthread_t thread);
              //分离线程,被分离的线程退出后,自动回收资源,不能被等待
              pthread_detach(tid);
              
     3.线程安全概念: 
          线程安全:多个执行流之间对数据竞争操作--不安全
          线程安全问题: 因为多个执行流之间对数据竞争操作,有可能造成数据二义性问题
          如何实现线程安全: 互斥锁,条件变量,生产者消费者模型,信号量,读写锁,
              (1)同步与互斥
                   同步:保证临界资源访问的时序可控性
                   互斥:保证临界资源的同一时间的唯一访问性
              互斥的实现:
                 互斥锁:
                 int pthread_mutex_destory(pthread_mutex_t *mutex);
                 //pthread_mutex_t mutex 互斥锁变量
                 
                 pthread_mutex_init(pthread_mutex_t *restrict mutex,
                           const pthread_mutexattr_t *restrict attr)
                 //互斥锁初始化
                 //attr   互斥锁变量,通常置NULL

                int pthread_mutex_lock(pthread_mutex_t *mutex);
                //加锁(加锁之后要在任意可能退出线程的地方都要解锁)
                 pthread_mutex_lock(&mutex);
                 
                 int pthread_mutex_unlock(pthread_mutex_t *mutex);
                //解锁
                 pthread_mutex_unlock(&mutex);
                 
                 pthread_mutex_destroy(&mutex);
                 //互斥锁销毁
                 
             死锁:
                 产生场景:锁资源的竞争以及加锁/解锁顺序不当
                 死锁如何产生的:四个必要条件
                       互斥条件: 我操作的时候别人不能操作
                       不可剥夺条件: 我加的锁,别人不能解锁
                       请求与保持条件: 占用资源的同时申请新的资源,新资源申请不到,老资源也不释放
                       环路等待条件: 1占着A资源,申请B资源;同时2占着B资源,申请A资源,两边1和2都会进入等待
                 死锁的预防:
                      破坏必要条件(除互斥外)----如何破坏
                 死锁的避免:银行家算法,死锁检测算法
             (2) 条件变量: 用于进程间同步--唤醒与等待
                   条件满足则唤醒等待的进程,条件不满足则等待
                   pthread_cond_t  cond                定义
                   pthread_cond_init()                 初始化
                   pthread_cond_wait(&cond,&mutex)     等待
                   pthread_cond_destory()              销毁
                   pthread_cond_signal()               唤醒至少一个等待的线程
                   pthread_cond_broadcast(&cond)       唤醒所有等待的进程
             条件变量与互斥锁搭配使用:
                  因为条件变量并不具备操作条件判断的功能,对条件的判断是一个临界资源需要加锁
                条件变量的pthread_cond_wait中集合了解锁+休眠+被唤醒后加锁的功能(原子操作)
                  如果对临界资源操作的线程有多种角色,需要分别等待到不同的条件变量上,分别进行唤醒
                这样才不会造成唤醒角色错误的问题
            (3) 生产者消费者模型:
                   多个生产者生产产品,放在队列中
                   多个消费者消费产品,从队列中获取
                        向队列中同时放数据或同时从队列中取数据就会出问题
                    生产者与消费者之间实现安全操作:
                        生产者与生产者之间是互斥关系
                        消费者与消费者之间是互斥关系
                        生产者与消费者之间是同步+互斥关系
                    实现生产者与消费者模型代码:C++
                         实现的主要是一个线程安全的队列
                   生产者生产数据,消费者获取数据处理;实现线程安全的数据操作
                   三种关系:生产者与生产者,消费者与消费者,生产者与消费者
                    一个场所,两类角色,三种关系
                    作用:解耦合,支持忙闲不均,支持并发
            (4)posix标准信号量: 具有等待队列的计数器--主要实现线程/进程间的同步与互斥
                   原理: 可以初始化一个资源计数
                          当获取资源时,先判断计数;
                              若>0 ,表示有资源,则计数减一,直接返回,获取资源
                              若<0 ,表示没有资源,则阻塞等待
                          当资源产出,计数+1,唤醒等待队列上的进程/线程
                    通过自身计数的判断+等待+唤醒4
                    posix标准信号量接口操作步骤:
                      sem_init()       初始化信号量
                      sem_wait       计数-1 后小于0 ,则等待;否则直接返回
                      sem_post       若资源产出,计数+1操作,唤醒等待
                      sem_destroy    销毁
                      
                      int sem_init(sem_t *sem,int pshared,unsigned int value);
                      //  sem:     信号量
                      //  pshared:
                         //      0    用于线程间同步与互斥
                      //     !0    用于进程间同步与互斥
                      //  value: 信号量的计数初值
                      
                      int sem_wait(sem_t* sem);
                      //  计数判断,若<=0;则阻塞等待
                      int sem_trywait(sem_t* sem);
                      //  计数判断,若<=0;则报错返回
                      int sem_timedwait(sem_t* sem,struct timespec * abs_timeout)
                      //计数判断,若<=0;则限时等待
                条件变量和信号量实现同步的区别:
                     条件变量是通过外部条件来判断是否等待;
                     信号量是内部计数器来判断是否等待
            (5)读写锁:
                  写的时候,其他人即不能读也不能写
                  读的时候,可以同时读,但是不能写
                  写互斥,读共享
                  _write_count 若大于0     ,读锁和写锁都阻塞
                  _read_count  若大于0,则写锁阻塞,但是读锁可以加
                 读写锁加锁时的阻塞: 使用自旋锁实现---循环对资源进行判断
                         cpu消耗较大--自旋锁的使用场景是时间很短的情况                 
                        pthread_rwlock_t         读写锁定义
                        pthread_rwlock_init      读写锁初始化
                        pthread_rwlock_rdlock()  加读锁
                        pthread_rwlock_wrlock()  加写锁
                        pthread_rwlock_unlock    解锁
                  读写锁通常用于读者多,写者的情况少:这时候想要加写锁修改数据,
              但是一直都有线程/进程加读锁;导致写饥饿问题;这是不合适的
                  因此读写锁是有优先级的:分为写优先和读优先
                  读写锁默认是读优先     
                 int pthread_relock_setkind_np(pthread_,int pref)
                 //设置读写锁优先级                 
         
          线程池:一堆线程(有最大数量上限)+线程安全的任务队列
              作用: (1)避免大量线程创建/销毁的时间成本
                     (2)避免峰值压力,导致线程创建过多,资源耗尽,程序崩溃
                     (3)解耦合
                     (4)支持忙闲不均
                     (5)支持并发
              线程池中的线程从任务队列中获取任务,然后进行处理
              
              手撕线程池: 实现线程池类         实现任务类
                   (1)线程池中有最大数量限制---线程数量
                   (2)当前线程池中的线程数量
                   (3)线程安全的任务队列
                   (4)任务
          STL中线程安全:  不安全
          智能指针的线程安全:

          线程安全的单例模式:
              设计模式:针对经典应用场景,设计的解决方案
              单例模式:资源只能被加载一次/一个对象只能被实例化一次
              单例模式的实现:
                   饿汉模式:程序启动直接实例化对象(启动慢但运行流畅)
                   懒汉模式:用的时候才去实例化对象(启动快)
                   线程安全的单例模式(懒汉模式)
                       static data
                       lock
                       if(data==NULL){data=new[]}
                       unlock
                     注意:如果不加锁就不是线程安全的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值