进程与线程

声明:代码执行的环境是CentOS 7.6 64bit Linux

进程概念

<待续>

进程操作

<待续>

进程间通信

<待续>

进程信号

<待续>

线程概念

在这里插入图片描述
线程资源的大部分是共享的,而栈和上下文,信号屏蔽字和调度优先级等是独有的。

线程操作

线程操作准备:若查找不到线程操作相关函数,请在root用户下,输入如下命令安装man手册

yum -y install man-page

Linux对线程操作的库-原生线程库 原生线程库是系统级别工程师,在用户层对Linux轻量级进程系统接口进行封装,打包成的库。

下面的介绍都是对函数的概述,详细情况请参考man手册中的DESCRIPTION 详细描述


创建线程pthread_create

使用man手册查看函数含义

man pthread_create

在这里插入图片描述
创建好的线程Id会放入thread_t* 的参数中

在man手册内直接输出 /return value 可以快速的查看到返回值的定义

/return value

在这里插入图片描述

pthread_create验证实验代码

书写makefile
建议使用 -lpthread ‘ -l’选项表示连接某个库

thread:thread.c
	gcc -o $@ $^ -lpthread

.PHONY:clean
clean:
	rm -f thread

thread.c的目的 在main函数中调用pthread_create创建新线程,传入参数,在Thread_run回调函数中每隔着2秒打印传入的参数和自己的线程id,main函数创建新线程后每隔2秒输出新线程id信息。

额外调用的库函数pthread_self() 返回调用该函数的线程id号,可以使用man手册查看

man pthread_self()
  //代码这么写是存在问题的! 线程需要等待
  #include <stdio.h>
  #include <unistd.h> //sleep 函数的头文件
  #include <pthread.h>
  
  void* Thread_run(void* arg)
  {
      while(1)
      {
          printf("i am %s , id is %lu\n", (char*)arg , pthread_self());
         sleep(2);
     }
 }
 
 int main()
 {
     pthread_t threadId;
     pthread_create(&threadId, NULL, Thread_run, (void*)"running");
 
     while(1)
     {
		printf("new threadId is %lu\n", threadId);
        sleep(2);
     }
     return 0;
 }

线程等待pthread_join

为什么要等待线程:如果不去等待线程,那么线程退出后所占用资源不会被回收,这部分资源就找不回来了,直到程序退出操作系统自动回收。我们使用的资源当然要哪里拿的放到哪里。

使用man手册查看函数含义

man pthread_join

在这里插入图片描述
其实看到这里的函数名,我就很疑惑为什么目的是等待,不叫pthread_wait,而是join,我感觉很奇怪,???

PTHREAD_CANCELED 值是(void*)-1
pthread_join成功返回0,失败返回错误码。


线程终止pthread_exit

使用man手册查看函数含义

man pthread_exit

在这里插入图片描述


线程取消pthread_cancel

使用man手册查看函数含义

man pthread_cancel

在这里插入图片描述


线程分离pthread_detach

使用man手册查看函数含义

man pthread_detach


当想不去等待线程(pthread_join)时使用,分离后的线程执行完后会自动回收资源。


线程操作函数使用

文件:makefile,thread.c
目的:在thread.c文件中一次性创建 4个线程,传参1,2,3,4在Thread_run函数中区分他们,main函数内等待他们,各线程在Thread_run打印各自如何退出,main函数等待他们后打印pthread_join等待情况,返回0代表join成功,和打印返回参数的情况。

#include <stdio.h>
#include <unistd.h> //sleep 函数的头文件
#include <pthread.h>
#include <stdlib.h>

void* Thread_run(void* arg)
{
	sleep(4);//让创建新线程成功先打印,新线程怎么做后打印

	int flag = *(int*) arg;
	if(flag == 1)
	{
		pthread_detach(pthread_self());
		printf("新线程%d<%lu>分离!\n",flag, pthread_self());
		sleep(10);
	}
	else if(flag == 2)
	{
		printf("新线程%d<%lu>return退出!\n",flag, pthread_self());
		sleep(2);
		int* ret = (int*)malloc(sizeof(int));
		*ret = 2;
		return (void*)ret;
	}
	else if(flag == 3)
	{		
		printf("新线程%d<%lu>pthread_exit退出!\n",flag, pthread_self());
		sleep(2);

		int* ret = (int*)malloc(sizeof(int));
		*ret = 3;
		pthread_exit((void*)ret);
	}
	else if(flag == 4)
	{	
		while(1)
		{
			sleep(1);
			printf("新线程%d<%lu>pthread_cancel退出!\n",flag, pthread_self());
		}
	}
	else
	{
		printf("线程判断异常!\n");
		exit(-1); //退出进程
	}
}

int main()
{
	//存储线程Id 和线程标识
	pthread_t threadIds[4];
	int index[4] = {1,2,3,4};//传入值区分1,2,3,4线程

	//创建4个线程
	int i = 0;
	for(i = 0; i < 4; i++)
	{
		pthread_create(threadIds+i, NULL, Thread_run, (void*)(index+i));
		printf("创建新线程%d<%lu> 成功!\n", index[i], threadIds[i]);
	}

	pthread_cancel(threadIds[3]); //取消线程4

	//等待线程2,3,4 对于detach线程只打印信息
	for(i = 0; i < 4; i++)
	{
		if(i == 0) //线程1 情况
		{
			printf("线程%d<%lu> 被回收\n", index[i], threadIds[i]);
		}
		else //线程2 3 4 打印返回的信息 与 pthread_join执行情况
		{
			void* retval = NULL;
			int flag = pthread_join(threadIds[i], &retval);
			
			if(i == 3) //新线程3 是被取消线程 处理-1的值
			{	
				printf("线程%d<%lu> join_ret = %d, retval = %d\n", index[i], threadIds[i], flag, (int)retval);
			}
			else
			{
				printf("线程%d<%lu> join_ret = %d, retval = %d\n", index[i], threadIds[i], flag, *(int*)retval);
				free(retval);
			}
		}	
	}
	return 0;
}

在这里插入图片描述
可以调整线程休眠时间,使用ps -aL查看线程情况

ps -aL

线程互斥锁mutex

一个函数是线程安全的不一定是可重入的,一个函数是可重入的一定是线程安全的。
死锁四个必要条件:1.互斥 2.请求与保持条件 3.不剥夺 4循环等待

使用互斥锁(mutex)
1.创建pthread_mutex_t类型的变量,这就是一把锁
2.使用pthread_mutex_init()初始化锁
3.进入临界区时使用pthread_mutex_lock锁住临界区
4.出临界区时使用pthread_mutex_unlock释放锁资源
注意:这些函数的细节可以使用man手册阅读了解

线程互斥锁的使用:
目的:主线程创建5个线程去抢票,之后主函数等到5个线程结束回收资源,票属于临界资源,设计一个票对象,完成互斥接口GetTicket供线程使用。票对象使用互斥锁使得票的获取是互斥的。将线程抢票情况打印。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <cstring>

class Ticket
{
private: 
    int _nums; //票的个数
    pthread_mutex_t _mtx; //互斥锁
public:
    Ticket()
        :_nums(20)
    {
        pthread_mutex_init(&_mtx, nullptr);
    }

    void GetTicket()
    {
    	//对临界区上互斥锁
        pthread_mutex_lock(&_mtx);
        if(_nums > 0)
        {
            std::cout << "线程" << pthread_self() << "获得一张票" << _nums <<std::endl;
            _nums--;
        }
        else
        {
             std::cout << "票卖完了!" << std::endl;
        }
        pthread_mutex_unlock(&_mtx);
    }

    ~Ticket()
    {
        pthread_mutex_destroy(&_mtx);
    }
};


void* TaskRun(void* arg)
{
    Ticket& ticket = *(Ticket*)arg;
    while(1)
    {
        sleep(1);
        ticket.GetTicket();
    }
}

int main()
{
    Ticket ticket;
    //创建线程
    pthread_t tId[5];
    for(int i = 0; i < 5; i++)
    {
        int* arg = new int(i);
        pthread_create(tId+i, nullptr, TaskRun, (void*) &ticket);
    }

    //等待线程
    for(int i = 0; i < 5; i++)
    {
        void* ret = nullptr;
        pthread_join(tId[i], &ret);
    }
    return 0;
}

在这里插入图片描述


线程同步

线程同步是指线程按照某种条件,协调的去完成某件任务。

pthread_cond_wait


pthread_cond_wait是让调用线程,在某个条件变量下等待,同时释放调用线程持有的互斥锁。


pthread_cond_signal


pthread_cond_signal是唤醒某条件变量下等待的一个线程。


pthread_cond_broadcast


pthread_cond_broadcast是唤醒某条件变量下等待的全部线程。


mutex 与 cond 的生产消费模型 阻塞队列

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

namespace chyx
{
    const int DEFAULT_CAP = 5;  //消费队列默认 元素上限

    template <class T>
    class BlockQueue
    {
    private:
        std::queue<T> _bq;      //生产消费队列
        int _cap;               //队列元素上限
        pthread_mutex_t _mtx;   //保护临界资源的锁
        pthread_cond_t _full;   //消费者在 full条件变量下等待
        pthread_cond_t _empty;  //生产者在 empty条件变量下等待

    private:
        bool IsFull()
        {
            return _bq.size() >= _cap ? true : false;
        }

        bool IsEmpty()
        {
            return _bq.size() == 0 ? true : false;
        }

        void LockQueue()
        {
            pthread_mutex_lock(&_mtx);
        }

        void UnlockQueue()
        {
            pthread_mutex_unlock(&_mtx);
        }

        void ProducterWait()
        {
            pthread_cond_wait(&_empty, &_mtx);
        }

        void ConsumerWait()
        {
            pthread_cond_wait(&_full, &_mtx);
        }

        void WakeUpComsumer()
        {
            pthread_cond_signal(&_full);
        }

        void WakeUpProducter()
        {
            pthread_cond_signal(&_empty);
        }

    public:
        BlockQueue(int cap = chyx::DEFAULT_CAP)
            :_cap(cap)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_full, nullptr);
            pthread_cond_init(&_empty, nullptr);
        }

        ~BlockQueue()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_full);
            pthread_cond_destroy(&_empty);
        }

    public:
        //无关紧要的注释,参数类型一般约定
        //输入参数 const& 
        //输出参数 *
        //输入输出参数 &


        void Push(const T& in)
        {
            //向队列放入 数据
            LockQueue();

            //注意这里一定要写while而不是if
            while(IsFull())
            {
                ProducterWait();
            }
            _bq.push(in);
            WakeUpComsumer();
            UnlockQueue();
        }

        void Pop(T* out)
        { 
            //从队列拿出 数据
            LockQueue();

            //注意这里一定要写while而不是if
            while(IsEmpty())
            {
                ConsumerWait();
            }

            *out = _bq.front();
            _bq.pop();
            WakeUpProducter();
            UnlockQueue();
        }
    };
}

测试程序:主线程创建两个线程后等待,一个去消费,一个去生产。达到交替打印数据。

#include <cstdlib>
#include "BlockQueue.hpp"
#include <unistd.h>

//消费者任务函数
void* Customer(void* arg)
{
    chyx::BlockQueue<int>* bq = (chyx::BlockQueue<int>*)arg;
    while(true)
    {
        int product;
        bq->Pop(&product);
        std::cout << "Customer Buy:" << product << std::endl;
        sleep(2);
    }
}


//生产者任务函数
void* Producter(void* arg)
{
    chyx::BlockQueue<int>* bq = (chyx::BlockQueue<int>*)arg;
    while(true)
    {
        int product = rand() % 50;
        std::cout << "Producter Make:" << product << std::endl;
        bq->Push(product);
        sleep(1);
    }
}


int main()
{
    srand((unsigned int)time(nullptr));

    //主函数创建线程,与阻塞队列,然后等待线程。
    chyx::BlockQueue<int>* bq = new chyx::BlockQueue<int>();

    pthread_t cId;
    pthread_t pId;

    pthread_create(&cId, nullptr, Customer, (void*)bq);
    pthread_create(&pId, nullptr, Producter, (void*)bq);
    

    void* ret = nullptr;
    pthread_join(cId, &ret);
    pthread_join(pId, &ret);

    return 0;
}

信号量 semaphore

信号量是一个计数器,它是去描述资源数目多少的。
以下的信号量的调用函数接口,本身就是原子的。
sem_init

sem_init是去初始化一个信号量的,pshare是信号量的共享属性,value是信号量的初始值是多少


sem_destroy

sem_destroy是回收一个信号量的。


sem_wait


sem_wait是使信号量减少1,如果调用这个信号量为0,则会阻塞式等待申请。


sem_post

sem_post是使信号量加1。


## 信号量的生产消费模型 环形队列
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>

namespace chyx
{
    const int DEFAUT_CAP = 5;

    template<class T>
    class RingQueue
    {
    private:
        std::vector<T> _rq;     //循环队列
        int _cap;               //循环队列的容量
        int _comsumerIndex;     //消费者 消费的位置
        int _producterIndex;    //生产者 生产的位置

        //维护生产者与消费者的同步关系
        sem_t _emptyLocation;   //生产者的 emptyLocation 信号量
        sem_t _dataLocation;    //消费者的 dataLocation 信号量

        //维护消费者与消费者,生产者与生产者的互斥关系
        pthread_mutex_t _consumerMtx;
        pthread_mutex_t _producterMtx;

    public:
        RingQueue(int cap = DEFAUT_CAP)
            :_cap(cap), _comsumerIndex(0), _producterIndex(0)
        {
            _rq.reserve(_cap);
            sem_init(&_emptyLocation, 0, _cap);
            sem_init(&_dataLocation, 0, 0);
            pthread_mutex_init(&_consumerMtx, nullptr);
            pthread_mutex_init(&_producterMtx, nullptr);
        }

        ~RingQueue()
        {
            sem_destroy(&_emptyLocation);
            sem_destroy(&_dataLocation);
            pthread_mutex_destroy(&_consumerMtx);
            pthread_mutex_destroy(&_producterMtx);
        }

    public: 
        void* Push(const T& in)
        {
            sem_wait(&_emptyLocation);  //申请 空位置信号量

            //对临界区上锁,保证互斥关系
            pthread_mutex_lock(&_producterMtx);

            _rq[_producterIndex] = in;  
            _producterIndex++;
            _producterIndex %= _cap;

            pthread_mutex_unlock(&_producterMtx);

            sem_post(&_dataLocation);   //释放 数据信号量
        }

        void* Pop(T* out)
        {
            sem_wait(&_dataLocation);  //申请 数据信号量

            //对临界区上锁,保证互斥关系
            pthread_mutex_lock(&_consumerMtx);

            *out = _rq[_comsumerIndex];
            _comsumerIndex++;
            _comsumerIndex %= _cap;

            pthread_mutex_unlock(&_consumerMtx);

            sem_post(&_emptyLocation);  //释放 空位置信号量
        }
    };
}

测试程序:主线程创建3组生产消费线程,生产线程都去生产,消费线程去消费,观察环形队列内数据情况。

#include "ring_queue.hpp"
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

void* Producter(void* args)
{
    chyx::RingQueue<int>* rq = (chyx::RingQueue<int>*) args;
    while(true)
    {
        //这里是任务处理部分,但是这里只是简单打印
        int data = rand() %1210;
        rq->Push(data);
        std::cout << pthread_self() << " Products: " << data << std::endl;
        sleep(1);
    }
}

void* Customer(void* args)
{
    chyx::RingQueue<int>* rq = (chyx::RingQueue<int>*) args;
    while(true)
    {
        //这里是任务处理部分,但是这里只是简单打印
        int data = 0;
        rq->Pop(&data);
        std::cout << pthread_self() << " Consume: " << data << std::endl;
        sleep(1);
    }
}

int main()
{
    srand((unsigned int)time(nullptr));

    chyx::RingQueue<int>* rq = new chyx::RingQueue<int>();  //创建循环队列生产消费模型

    //主线程创建3组生产者,消费者,后等待他们
    pthread_t pId1;
    pthread_t cId1;

    pthread_t pId2;
    pthread_t cId2;

    pthread_t pId3;
    pthread_t cId3;

    pthread_create(&pId1, nullptr, Producter, (void*)rq);
    pthread_create(&pId2, nullptr, Producter, (void*)rq);
    pthread_create(&pId3, nullptr, Producter, (void*)rq);
    pthread_create(&cId1, nullptr, Customer, (void*)rq);
    pthread_create(&cId2, nullptr, Customer, (void*)rq);
    pthread_create(&cId3, nullptr, Customer, (void*)rq);

    pthread_join(pId1, nullptr);
    pthread_join(cId1, nullptr);
    pthread_join(pId2, nullptr);
    pthread_join(cId2, nullptr);
    pthread_join(pId3, nullptr);
    pthread_join(cId3, nullptr);

    return 0;
}

简易的单例模式线程池

创建线程是为完成某个任务的,而向OS申请创建线程是有代价的,每次有任务来,再向OS申请的方式,在一些场景下,效率不如提前申请一批线程,当任务来时直接使用这批线程要效率高。而把这批提前准备好的线程称为线程池。
线程池的核心目的:提高效率。


#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

namespace chyx
{
    const int NUM = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int _num;                           //线程数量
        std::queue<T> _task_queue;          //管理线程的队列
        pthread_mutex_t _mtx;               //互斥锁 线程间竞争任务
        pthread_cond_t  _existence;         //条件变量 在任务队列为空时 线程在 existence条件变量下等待
        static ThreadPool<T>* _pThreadPool; //静态成员变量属于类 让外部获取该静态成员变量达到单例目的

    private:
        //单例模式:不让类定义对象 拷贝构造 赋值
        ThreadPool(int num = NUM)
            :_num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_existence, nullptr);
        }

        ThreadPool(const ThreadPool<T>& tp) = delete;

        ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete; 

        void InitThreadPool(void)
        {
            pthread_t tid;
            for(size_t i = 0; i < _num; i++)
            {
                pthread_create(&tid, nullptr, Routine, (void*)this);
            }
        }

    public:
        //静态方法!!! 供外部调用
        static ThreadPool<T>* GetThreadPool(void)
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            if(_pThreadPool == nullptr)
            {
                pthread_mutex_lock(&lock);
                if(_pThreadPool == nullptr)
                {
                    _pThreadPool = new ThreadPool<T>();
                    _pThreadPool->InitThreadPool();
                }
                pthread_mutex_unlock(&lock);
            }

            return _pThreadPool;
        }

    public:
        void Lock(void)
        {
            pthread_mutex_lock(&_mtx);
        }

        void Unlock(void)
        {
            pthread_mutex_unlock(&_mtx);
        }

        void Wait(void)
        {
            pthread_cond_wait(&_existence, &_mtx);
        }

        void WakeUp(void)
        {
            pthread_cond_signal(&_existence);
        }

        void WakeUpAll(void)
        {
            pthread_cond_broadcast(&_existence);
        }

        bool IsEmpty(void)
        {
            return _task_queue.size() == 0 ? true : false;
        }


   public:
        void PushTask(const T& in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();
            WakeUp();
        }

        void PopTask(T* out)
        {
            //这里没上锁的原因是:
            //在Routine中线程拿任务的过程是上锁的(串行的)

            *out = _task_queue.front();
            _task_queue.pop();
        }

        static void* Routine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T>* pThis = (ThreadPool<T>*)args; 

            //不停的处理任务
            while(true)
            {
                pThis->Lock();

                //任务队列为空时,让线程挂起
                while(pThis->IsEmpty())
                {
                    pThis->Wait();
                }

                //  打印自己是那个线程,处理任务前
                //std::cout << pthread_self() << std::endl;

                //走到这,一定存在任务
                T t;
                pThis->PopTask(&t);

                pThis->Unlock();

                t.Run();
            }
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_existence);
        }
    };
    template<class T>
    ThreadPool<T>* ThreadPool<T>::_pThreadPool = nullptr;
}

测试程序:

#pragma once 
#include <iostream>
#include <unistd.h>

namespace chyx
{
    class Task
    {
    public:
        Task(const float& x, const char& op, const float& y)
            :_x(x),_y(y),_op(op)
        {}

        Task()
        {}

        void Run()
        {
            float out = 0;
            if(_op == '+')
            {
                out = (_x + _y);
                std::cout << _x << _op << _y << " = " << out << std::endl;
            }
            else if(_op == '-')
            {
                out = (_x - _y);
                std::cout << _x << _op << _y << " = " << out << std::endl;
            }
            else if(_op == '*')
            {   
                out = (_x * _y);
                std::cout << _x << _op << _y << " = " << out << std::endl;
            }
            else
            {
                std::cout << _op << "is error" << std::endl;
            }
        }
    private:
        float _x;
        float _y;
        char _op;
    };
}
#include "thread_pool.hpp"
#include "task.hpp"
#include <ctime>
#include <cstdlib>
#include <stdlib.h>

int main()
{
    srand((unsigned int)time(nullptr));

    while(true)
    {
        float x = (float)(rand() % 100);
        float y = (float)(rand() % 100);
        char op[4] = "+-*";

        chyx::Task t(x, op[rand()%3], y);

        chyx::ThreadPool<chyx::Task>::GetThreadPool()->PushTask(t);
        sleep(2);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

E.d cheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值