进程与线程
声明:代码执行的环境是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;
}