多线程

多线程

线程概念
线程是什么?

在一个程序里的一个执行路线就叫做线程(thread)。线程是一个进程内部的控制序列。一切进程至少都有一个执行线程。

进程和线程

进程是资源分配的基本单位
线程是资源调度的最小单位
线程共享进程数据,但也拥有自己的一部分数据:线程ID>一组寄存器>栈>errno>信号屏蔽字>调度优先级

进程和线程的关系:在这里插入图片描述

线程之间的独有与共享

独有:栈,寄存器,信号屏蔽字,errno,线程ID
共享:虚拟地址空间(数据段/代码段),文件描述符,信号处理方式,当前工作目录,用户id/组id

进程和线程的区别

进程=火车,线程=车厢

  1. 线程在进程下行进(单纯的车厢无法运行)
  2. 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  3. 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  4. 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  5. 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  6. 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  7. 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  8. 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
  9. 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
多线程与多进程任务处理的优缺点分析

优点

  1. 创建一个新线程的代价比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速IO操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. IO密集型应用,为了提高性能,将IO操作重叠。线程可以同时等待不同的IO操作。

缺点

  1. 性能损失
  2. 健壮性降低
  3. 缺乏访问控制
  4. 编程难度提高
线程控制

线程控制分为:线程创建/线程终止/线程等待/线程分离

线程控制的接口都是库函数实现的:创建一个用户态线程让用户控制,但是程序的调度处理都是通过轻量级进程pcb实现的。

POSIX线程库

  1. 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  2. 要使用这些库函数,要通过引入头文件<pthread.h>
  3. 链接这些线程函数库时,要使用编译器命令的"-lpthread"选项
线程创建
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);
参数:
thread:返回线程ID--线程地址空间在整个虚拟地址空间中的首地址 
attr:设置线程的属性,为NULL表示使用默认属
start_routine:是个函数地址,线程启动后要执行的函数     
arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码

线程id:
tid-线程地址空间首地址-方便用户操作线程
pcb->pid 轻量级进程ID–LWP
pcb->tgid 线程组ID-进程ID == 首线程的pid
线程终止
return pthread_exit(void *retval) pthread_cancel(pthread_t tid)
线程等待
等待指定线程退出,获取这个线程的退出返回值,并且回收这个线程的资源;
一个线程有一个默认属性:joinable;处于joinable属性的线程退出后为了保存返回值,因此不会自动释放资源;如果不进行等待则会造成资源泄露
一个线程也只有处于joinable状态的时候,才需要被等待。
pthread_join(pthread_t tid,void **retval)

线程分离

线程分离就是将线程joinable属性修改为detach属性

线程若处于detach属性,则线程退出后将自动回收资源;并且这个线程不需要被等待,等待是毫无意义的,因为线程退出返回值占用的空间已经被回收了

pthread_detach(pthread_t tid)

线程分离的适用场景:

一进入自己的线程就线程等待:pthread_detach(pthread_self);

线程安全

多个线程同时对临界资源进行访问而不会造成数据的二义性

同步:时序合理性

互斥:对临界资源访问的唯一性

线程间互斥的实现:互斥锁(只有0/1的原子操作的计数器 + 等待队列)

  1. 定义互斥锁变量 pthread_mutex_t

  2. 对互斥锁变量进行初始化 pthread_mutex_init(&mutex,&attr) 或者 赋值初始化

  3. 对临界资源操作之前先加锁 pthread_mutex_lock(&mutex)

    ​ 若可以加锁则直接修改计数,函数返回;否则挂起等待

    ​ pthread_mutex_trylock / pthread_mutex_timedlock

  4. 对临界资源操作完成后进行解锁
    pthread_mutex_unlock(&mutex);

  5. 销毁互斥锁
    pthread_mutex_destory(&mutex);

#include <pthread> 
int ticket = 100;
void *yellow_bull(void *arg)
{
    while(1)
    {
        //int pthread_mutex_trylock(pthread_mutex_t *mutex);
           //若不能加锁,立即报错返回
         //int pthread_mutex_lock(pthread_mutex_t *mutex);
        // _mutex:互斥锁变量
        pthread_mutex_lock();
        if(ticket > 0 )
        {
            printf("bull %d get a ticket:%d\n",(int)arg,ticket);
            ticket--;
        }
        else
        {
            printf("have no tickets,bull %d exit\n",(int)arg);
            //用户加锁之后,需要在任意有可能退出线程的地方进行解锁
             pthread_mutex_unlock(&mutex);
            pthread_exit(NULL);
        }
        //int pthread_mutex_unlock(pthread_mutex_t *mutex);
        pthread_mutex_unlock(&mutex);
        
    }
}
int main()
{
    
    pthread_t tid[4];
    // 
    // mutex  互斥锁变量
    // 
    pthread_mutex_init(&mutex,NULL);
    
     int i;
    for(i=0 ;i < 4;i++)
    {
        int ret = pthread_creat(&tid[i],NULL,yellow_bull,(void*)i);
        if(ret != 0)
        {
            printf("thread craete error\n");
            return -1;
        }
    }
    for (int i = 0;i < 4;i++)
        pthread_join(tid[i],NULL);
	return 0;
}
死锁

多个线程对锁资源进行竞争访问,但是因为推进顺序不当,导致相互等待,使程序无法往下运行。、

死锁产生的四个必要条件:

  1. 互斥条件 一个锁只有一个线程可以获取
  2. 不可剥夺条件 我加的锁别人不能解
  3. 请求与保持条件 拿着A锁 去请求B锁,但是获取不到B锁,也不释放A锁
  4. 环路等待条件 我拿着A锁请求B锁,对方拿着B锁请求A锁

死锁预防: 破坏四个必要条件

死锁避免: 死锁检测算法,银行家算法

线程间同步的实现

等待 + 唤醒

操作条件不满足则等待,别人促使条件满足后唤醒等待

条件变量:

​ 条件变量实现同步:

​ 线程在对临界资源访问之前,先判断是否能够操作;若可以操作则线程直接操作;

​ 否则若不能操作;则条件变量提供等待功能;让pcb等待在队列上

​ 其他线程促使操作条件满足,然后唤醒条件变量等待队列上的线程

  1. 定义条件变量 pthread_cond_t

  2. 条件变量初始化 pthread_cond_init(&cond,&attr);

  3. 用户在判断条件不满足的情况下提供等待功能 pthread_cond_wait(&cond,&mutex)/pthread_cond_timewait();

    ​ 为什么条件变量要与互斥锁一起使用:线程什么时候等待,需要一个判断条件;而这个判断条件也是一个临界资源(等待了之后,其他线程需要促使这个条件满足(修改这个临界资源));因此这个临界资源的操作就需要受保护(默认使用互斥锁实现保护)

  4. 用户在促使条件满足后,唤醒等待 pthread_cond_signal(&cond)(至少唤醒一个) / pthread_cond_broadcast(&cond)

  5. 销毁条件变量pthread_cond_destory(&cond)

    //实现条件变量的基本使用
    //以自己吃面和厨师做面为例子:
    //自己想要吃面,前提是有面,没有面则等待
    //厨师做面,做好面后,唤醒吃面的人
    #include<stdio.h>
    #include<unistd.h>
    #include<pthread.h>
    int _have_noodle = 0;
    pthread_mutex_t mutex;
    pthread__cond_t cond_cook;
    pthread__cond_t cond_eat;
    void *eat_noodle(void *arg)
    {
        while(1)
        {
            pthread_mutex_lock(&mutex);
            while(_have_noodle == 0)
            {
                //没有面,就不能吃面
                //usleep(1000);
                //continue;
                //int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                 // pthread_mutex_t *restrict mutex,
                //  const struct timespec *restrict abstime);
                //限时等待,等待超时后报错返回
                
                //int pthread_cond_wait(pthread_cond_t *restrict cond,
                //  pthread_mutex_t *restrict mutex);
    			//一直阻塞等待
                
                // pthread_cond_wait实现三步:
                //1.解锁
                //2.休眠
                //3.被唤醒后加锁
                //其中解锁和休眠操作必须是原子操作
                pthread_cond_wait(&cond_eat,&mutex);
            }
            //能走下来表示have_noodle==1,表示有面
            printf("eat noodle,decilious\n");
            _have_noodle = 0;
             pthread_cond_signal(&cond_cook);
        }
    }
    
    void *cook_noodle(void *arg)
    {
        while(1)
        {
            while(_have_noodle == 1)
            {
                //现在有面,但是没人吃,不能继续做了
               // continue;
                  pthread_cond_wait(&cond_cook,&mutex);
    		}
            printf("cook nooldle,come on\n");
            _have_noodle = 1;
             pthread_cond_signal(&cond_eat);
             pthread_mutex_unlock(&mutex);
        }
        return NULL;
    }
    int main()
    {
        pthread_t tid1,tid2;
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond_eat,NULL);
         pthread_cond_init(&cond_cook,NULL);
        
        for(int i = 0;i < 4;i++)
        {
             int ret = pthread_create(&tid1,NULL,eat_noodle,NULL);
        if(ret != 0)
        {
            printf("pthread create error\n");
            return -1;
        }
        }
        
         for(int i = 0;i < 4;i++)
        {
             int ret = pthread_create(&tid2,NULL,eat_noodle,NULL);
        if(ret != 0)
        {
            printf("pthread create error\n");
            return -1;
        }
        }
       
        
        pthread_join(tid1,NULL);
        pthread_join(tid2,NULL);
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond_eat);
         pthread_cond_destroy(&cond_cook);
        
        return 0;
    }
    

    条件变量的条件判断应该是一个循环判断:

    ​ 多个顾客线程若同时被唤醒,只有一个顾客可以加锁,其他的顾客线程将阻塞在加锁上(而不是条件变量的等待);

    ​ 第一个加锁的顾客开始吃面,吃碗面后进行解锁,这时候,获取到锁的线程有可能是一个顾客线程,因为再次判断有没有面,因此直接吃面,但是面已经被第一个顾客线程吃掉了,因此逻辑错误应该加锁之后重新再次判断是否有面。

    不同角色应该等待在不同的条件变量上:

    ​ 在有多个厨师线程和顾客线程的时候,若是顾客和厨师线程都等待在同一个条件变量的等待队列中,会导致厨师做了一碗面,本应该唤醒顾客线程吃面,但是这时候有可能唤醒的是一个厨师线程,而厨师线程因为循环判断有没有面,因为已经有面而陷入等待(而顾客线程因为没有被唤醒而无法吃面)

生产者与消费者模型

​ 一个场所,两种角色,三种关系

优点(解决的问题):

  1. 解耦
  2. 支持并发
  3. 支持忙闲不均

这些优点都是通过这个场所来提供的,但是因为多个角色有可能同时操作场所,因此要保证场所的操作安全。

三种关系:

  1. 生产者与生产者互斥关系;
  2. 消费者与消费者互斥关系;
  3. 生产者与消费者具有同步+互斥的关系;

BlockQueue

#include<iostream>
#include<queue>
#include<pthread.h>
#define MAX_QUEUE 10
class BlockQueue
{
    private:
    std::queue<int> _queue;
    int _capacity;//用于限制队列中最大节点数量
    pthread_mutex_t _mutex;
    pthread_cond_t _cond_product;
    pthread_cond_t _cond_consumer;
    public:
    	BlockQueue(int cap = MAX_QUEUE):_capacity(cap)
        {
            pthread_mutex_init(&mutex,NULL);
            pthread_cond_init(&cond_eat,NULL);
            pthread_cond_init(&cond_cook,NULL);
        }
    
    	~BlockQueue()
        {
            pthread_mutex_destroy(&mutex);
   			pthread_cond_destroy(&cond_eat);
   		    pthread_cond_destroy(&cond_cook);
        }
    	bool QueuePush(int data)
        {
         	pthread_mutex_lock(&_mutex);
            while(_queue.size() == _capacity)
            {
                pthread_cond_wait(&_cond_product,&_mutex);
            }
            _queue.push(data);
            pthread_cond_signal(&_cond_consumer);
            pthread_mutex_unlock(&_mutex);
        }
    	bool QueuePop(int &data)
        {
            pthread_mutex_lock(&_mutex);
            while(_queue.empty())
            {
                pthread_cond_wait(&_cond_consumer, &_mutex);
            }
            data = _queue.front();
            _queue.pop();
            pthread_cond_signal(&_cond_product);
            pthread_mutex_unlock(&_mutex);
        }
};
void *thr_consumer(void *arg)
{
    BlockQueue *q = (BlockQueue*)arg;
    while(1)
    {
        int data;
        q->QueuePop(data);
        std::cout<<"consumer:"<<pthread_self()<<"get data:"<<data<<"\n";
    }
    return NULL;
}
void *thr_product(void *arg)
{
     BlockQueue *q = (BlockQueue*)arg;
    int i =0;
    while(1)
    {
      q->QueuePush(i++);
         std::cout<<"product:"<<pthread_self()<<"put data:"<<i<<"\n";
    }
    return NULL;
}
int main()
{
    pthread_t con_tid[4],pro_tid[4];
    BlockQueue q;
    for(int i = 0;i < 4;i++)
    {
        int ret = pthread_create(&con_tid[i],NULL,thr_consumer,(void*)&q);
    if(ret != 0)
    {
        std::cout<<"pthread create error\n";
        return -1;
    }
    }
    
    for(int i = 0;i < 4;i++)
    {
        int ret = pthread_create(&cpro_tid[i],NULL,thr_product,(void*)&q);
    if(ret != 0)
    {
        std::cout<<"pthread create error\n";
        return -1;
    }
    }
    
     for(int i = 0;i < 4;i++)
    {
       pthread_join(con_tid[i],NULL);
    }
       for(int i = 0;i < 4;i++)
    {
       pthread_join(pro_tid[i],NULL);
    }
    return 0;
}

信号量

功能:实现线程间同步互斥

本质:一个计数器(做资源计数-判断当前是否可以对临界资源进行操作)+ 等待队列 + 等待 + 唤醒

原理:

​ 互斥原理:只具有0/1计数时,就可以实现互斥

​ 初始计数为1,1表示当前只有一个线程能够获取资源,获取资源之后-1;

​ 计数+1:并且唤醒等待队列上的线程;

​ 计数为0,其他线程则进行等待,(将pcb加入到等待队列)

​ 同步原理:对程序逻辑进行控制(对临界资源合理操作控制);通过计数判断当前是否能够对临界资源进行 操作,不能操作(计数<=0),则等待;

​ 其他线程促使条件判断后,对计数+1,唤醒等待的线程

​ 条件变量实现同步与信号量的区别:

​ 1.信号量并不需要搭配互斥锁的使用

​ 2.信号量本身的计数就是能够操作临界资源的判断条件

流程:

​ 1.定义信号量:sem_t

​ 2.初始化信号量 int sem_init(sem_t *sem,int pshared,unsigned int value);

​ sem:信号量变量

​ pshared:0-用于线程间同步与互斥 !0-用于进程间同步与互斥

​ value:信号量计数器初始值

​ 3.判断计数是否可以对临界资源进行操作 int sem_wait(sem_t *sem);

​ sem:信号量变量

​ 对计数进行判断,是否>0;若>0则函数立即正确返回,返回前计数-1,程序流程继续向下可以操作临界资 源;若<=0,则认为当前不能对临界资源进行操作,则线程等待

使用信号量实现生产者与消费者模型:

​ 1.实现线程安全的环形队列

​ 2.

//实现线程安全的环形队列
class RingQueue
{
    std::vector<int> _queue;
    int _capacity;
    int _read;
    int _write;
    sem_t _lock;
    sem_t _idle_space;//生产者入队数据之前判断队列中是否有空闲空间;判断能否入队数据
    sem_t _data_space;//消费者获取数据之前判断有数据的空间有多少判断能否获取数据
}

实现:posix

//posix标准的信号量的基本操作
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
int ticket = 100;
sem_t sem;
void *thr_start(void *arg)
{
    while(1)
    {
        if(ticket>0)
        {
            // int sem_wait(sem_t *sem);
            //若当前计数<=0;则线程一直等待
       		// int sem_trywait(sem_t *sem);
            //若当前计数<=0;则线直接报错返回
 			//int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
            //若当前计数<=0;等待时间超时后报错返回
			sem_wait(&sem)
            printf("get a ticket:%d\n",ticket);
            ticket--;
        }
        else 
        {
            pthread_exit(NULL);
        }
        // int sem_post(sem_t *sem);
        //计数+1,促使其他线程
        sem_post(&sem);
    }
}

int main()
{
    int i,ret;
    ptthread_t tid[4];
    //int sem_init(sem_t *sem, int pshared, unsigned int value);
    sem_init(&sem,0,1);
     for(int i = 0;i < 4;i++)
    {
        int ret = pthread_create(tid[i],NULL,thr_start,NULL);
    if(ret != 0)
    {
       printf("pthread create error\n");
        return -1;
    }
    }
        for(int i = 0;i < 4;i++)
    {
       pthread_join(tid[i],NULL);
    }
    
    sem_destroy(&sem);
	return 0;
}


**********缺
//通过信号量实现生产者消费者模型
class RingQueue
{
    std::vector<int> _queue;
    int _capacity;
    int _read;
    int _write;
    sem_t _lock;
    sem_t _idle_space;//生产者入队数据之前判断队列中是否有空闲空间;判断能否入队数据
    sem_t _data_space;//消费者获取数据之前判断有数据的空间有多少判断能否获取数据
  public:
  	RingQueue(int maxque = MAX_QUEUE):_capacity(maxque),
    _queue(maxque)
    {
        sem_init(&_lock,0,1);
        sem_init(&_idle_space,0,_capacity);
        sem_init(&_data_space,0,0);
    }
    ~RingQueue()
    {
        sem_destroy(&_lock);
        sem_destroy(&_idle_space);
        sem_destroy(&_data_space);
    }
    bool QueuePush(int data)
    {
        sem_wait(&_idle_space);
        _queue.push_back(data);
        _write = (_write + 1) % _capacity;
        sem_post(&_lock);
        
        sem_post(&_data_space);
        return true;
    }
    bool QueuePop(int &data)
    {
        sem_wait(&_data_space);//通过数据空间计数判断是否有数据操作
        
        sem_wait(&_lock);
        data = _queue[_read];
        _read = (_read + 1) % capacity;
        sem_post(&_lock);
        
        sem_post(&idle_space);//空闲空间计数+1;唤醒生产者
        return true;
    }
}

void *productor(void *arg)
{
    RingQueue *q = (RingQueue*)arg;
    int i = 0;
    while(1)
    {
        std::out << "productor put a data: "<<i<<std::endl;
    }
}

线程池

至少一个线程 + 任务队列

在程序初始化时,创建固定数量的线程(最大数据限制),从任务队列中获取任务,进行处理

​ 作用:

​ 1.避免为大量请求创建线程,导致瞬间资源耗尽程序崩溃的问题

​ 2.避免大量线程频繁创建销毁所带来的时间成本

​ 线程的实现

#include <iostream>
#include <queue>
#include <pthread.h>
typedef void (*handler_t)(int data)
    class Task
    {
       public:
        void SetTask(int data,handler_t handler)
        {
            _data = data;
            _handler = handler;
        }
        void Run()
        {
            _handler(_data);
        }
       private:
        int _data;
        handler_t handle;
       
    };
class threadpool
{
 public:
    ThreadPool(int max):thr_max(max)
    {
        pthread_mutex_init(&_mutex,NULL);
        pthread_cond_init(&_con_cond,NULL);
        pthread_cond_init(&_pro_cond,NULL);
    }
    ~ThreadPool()
    {
         pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_con_cond);
        pthread_cond_destroy(&_pro_cond);
    }
    bool TaskPush(Task &t)
    {
        pthread_mutex_lock(&_mutex);
        _task_queue.push(t);
        pthread_cond_signal(&_con_cond);
        pthread_mutex_unlock(&_mutex);
        return true;
    }
    bool PoolInit()
    {
		int ret;
        pthread_t tid;
        for(int i = 0;i < _thr_max;i++)
        {
           ret = pthread_create(tid[i],NULL,thr_start,(void*)this);
             if(ret != 0)
   				 {
      				 printf("pthread create error\n");
    			    return false;
   				 }
        }
        _cur_thr++;
        pthread_detach(tid);
    }
    bool TaskPop(Task &t)
    {
        t = _task_queue.front();
        _task_queue.pop();
        return true;
    }
    static void *thr_start(void *arg)
    {
        ThreadPool *pool = (ThreadPool*)arg;
        pthread_mutex_lock(&_mutex);
        while(pool->QueueIsEmpty)
        {
            pthread_cond_wait(&_con_cond,&_mutex);
        }
        Task t;
        TaskPop(t);
        pool->QueueUnBlock();
        pthread_mutex_unlock(&_mutex);
        t.Run();
        return NULL;
    }
    void PoolQuit()
    {
        if(_quit_flag == false)
        while(_cur_thr > 0)
        {
            Consumer
        }
    }
 public:
    void QueueBlock()
    {
        pthread_mutex_lock(&_mutex);
    }
     void QueueUnBlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void ConsumerWait()
    {
        pthread_cond_wait(&_con_cond,&_mutex);
    }
    void ConsumerWakeup()
    {
        pthread_cond_signal(&_con_cond);
    }
    bool QueueIsEmpty()
    {
        return _task_quque.empty();
    }
 private:
    int _thr_max;
    int _cur_thr;
    std::queue<Task> _task_queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _pro_cond;
    pthread_cond_t _con_cond;
    TaskPush(Task &t);
    TaskPop(Task &t);
    void *thr_start(void *arg)
};
void thr_handle(int data)
{
    srand(time(NULL));
    int sec = rand() % 5;
    std::cout<<"thr:"<<pthread_self()<<"get data:"<<data<<"sleep"<<sec<<" sec\n";
    sleep(sec);
}
int main()
{
    ThreadPool pool;
    Task task;
    
    pool.PoolInit();
    for(int i = 0;i < 10;i++)
    {
        Task task;
        task->SetTask(i,thr_handle);
        pool.TaskPush(task);
    }
    pool.PoolQuit();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值