当前位置: 首页 > news >正文

Linux:信号和线程

处理信号好的三种方法:默认动作,忽略动作,自定义处理(信号的捕捉)

自定义的方法如下所示:

void hander()
{cout << "...." << endl;
}int main(){// 对信号的自定义捕捉,我们只要捕捉一次,后续一直有效/signal(2, hander);//对2号信号自定义方法,一直不产生2好信号,就不会被捕捉// 2. 可以对更多的信号进行捕捉?signal(3, hander);signal(4, hander);signal(5, hander);// 3. 2 号信号SIGINT默认是终止进程// 4. ctrl+c --- 就是给目标进程发送2号信号,因为SIGINT默认是终止进程// signal(3, hander);// signal(4, hander);// signal(5, hander);while(true){std::cout << "hello bit, pid: " << getpid() << std::endl;sleep(1);}}

发送信号:本质就是修改指定进程pcb(是一种内核数据结构,只有os才可以修改)中的信号的指定位图。

信号产生:

1.通过kill命令向指定进程发送信号(kill -x pid根据某个x信号使某个进程执行该信号)

2.键盘可以产生信号(ctrl+c(2信号)终止进程,ctrl+\(3信号)终止进程)。

3.系统调用(向指定进程发送指定信号)。

//    0    1   2
//./mykill 2 1234;给1234进程发送2号信号int main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " signum pid" << std::endl;return 1;}pid_t pid = std::stoi(argv[2]);int signum = std::stoi(argv[1]);kill(pid, signum);
}

Aborted(6号信号)信号就算自定义了,最后还会终止。

9号信号不允许自定义捕捉。

4.软件条件

 5.异常

程序异常向进程发送信号

终止进程的本质就是释放上下文的数据。

信号的保存:

实际执⾏信号的处理动作称为信号递达(信号的处理)

信号从产⽣到递达之间的状态,称为信号未决(Pending)。(已经产生了,还没有处理信号) 

进程可以选择阻塞(Block)某个信号(阻塞一个信号,则对应的信号一旦产生,一直不做处理)

信号保存的过程如下:

 信号的处理:

signal(2,handler);//自定义捕捉
signal(2,SIG_IGN);//忽略一个信号
signal(2,SIG_DFL);//默认处理动作

信号处理流程: 

 cpu中有一个寄存器,叫做ecx,里面可以存放如果是0就是内核态,3就是用户态,进而实现用户态和内核态之间的切换。

当前如果正在对信号x进行处理,默认x信号会自动屏蔽,当x信号处理完成时,会自动解除对x信号的屏蔽。

线程:

进程是资源分配的基本单位,线程是调度的基本单位。

操作系统对内存管理的基本的单位是4kb。

线程:在进程内部运行,是cpu调度的基本单位。

linux中的线程是虚拟存在的是进程模拟的,windows下的线程是真实存在的。

cpu看到的执行流<=进程(linux中的执行流是轻量级的进程)

ps -aL查看进程pid和线程LWP,当pid==LWP时,该线程为主线程。

创建进程的成本比较高,线程成本比较低(启动时)。

线程调度的成本比较低(运行时);因为:cpu中有一个寄存器cache,运行时会提前将数据和代码刷新到cache中,然后再cache中执行程序,当进程调度时需要将cache进行清空并重新刷新,线程调度不需要,因为线程调度cache中的代码是共享的,即 共享地址空间。所以成本低。(面试)

线程缺点:

健壮性降低 :在⼀个多线程程序⾥,因时间分配上的细微偏差或者 因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的,换句话说线程之间是缺乏保护 的。

缺乏访问控制 :进程是访问控制的基本粒度,在⼀个线程中调⽤某些OS函数会对整个进程造成影响。

线程中私有的部分:

一组寄存器(重要),栈(重要),线程ID,信号屏蔽字,调度优先级

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃,进程终⽌,该进程内的所有线程也就随即退出。

进程中的多个线程共享同⼀地址空间,因此TextSegment、DataSegment都是共享的,如果定义⼀个函数,在各线程中都可以调 ⽤,如果定义⼀个全局变量,在各线程中都可以访问到。

线程创建的系统调用:

实现代码如下:

// 新线程
void* threadStart(void* args)
{while (true){sleep(1);std::cout << "new thread running..." << ", pid: " << getpid()<< ", gval: " << gval << ", &gval: " << &gval << std::endl;}
}
int main()
{pthread_t tid1;pthread_create(&tid1, nullptr, threadStart, (void*)"thread-new");// 主线程while (true){std::cout << "main thread running..." << ", pid: " << getpid()<< ", gval: " << gval << ", &gval: " << &gval << std::endl;}return 0;
}

线程等待:新线程必须被主线程等待

int main()
{pthread_t tid; // unsigned long int// 问题1: main 和 new 线程谁先运行?不确定//(void*)&td参数可以传任意类型,也可以是自定义类型int n = pthread_create(&tid, nullptr, threadRun, (void*)&td);if(n != 0) // 后面我们暂时就关心了{std::cerr << "create thread error" << std::endl;return 1;}// 问题2:期望谁最后退出? main thread,如何保证呢n = pthread_join(tid, nullptr); // join来保证。 不join会造成类似僵尸进程的问题if(n == 0){std::cout << "main thread wait success, new thread exit code: " << result->print() << std::endl;}return 0;
}

 在上述代码中:线程的id是一个虚拟地址。

//新线程函数的返回只考虑正确的返回,不考虑异常,异常了整个进程就崩溃了包括主线程。
//也可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
void *threadRun(void *args)
{int cnt = 10;while(cnt){sleep(3); std::cout << td->name << " run ..." << ", cnt: " << cnt-- << std::endl;int *p = nullptr;*p = 100; // 故意野指针}delete td;return (void*)0;
}

多线程创建: 

  int main()//线程的创建和等待
{// vector<pthread_t> tids;// //6.创建多线程// for(int i=0;i<num;i++)// {//     //1.线程的id//     pthread_t tid;//     //2.线程的name//     char *name= new char[128];//     snprintf(name,128,"thread-%d",i+1);//     pthread_create(&tid,nullptr,threadrun,/*线程的名字*/name);//     //3.保存所有信息的id//     tids.emplace_back(tid);// }// //创建多线程的等待// for(auto tid:tids)// {//     void* name = nullptr;//     pthread_join(tid,&name);// }
void* threadrun(void* args)//创建多线程需要的新线程函数
{//pthread_detach(pthread_self());//线程分离string name = static_cast<const char*>(args);while (true){cout << name << " is runing" << endl;sleep(1);}//exit(1)专门用来终止进程的,不能用来终止线程//return args;//pthread_exit(args);//专门终止一个线程
}
const int num = 10;
//main函数结束,main thread结束,表示进程也就结束
int main()//线程的创建和等待
{//7.新线程的终止方法://(1)函数return//(2)pthread_exit()专门一个终止线程//(3)int pthread_cancel(pthread_t thread)取消线程前提是线程要存在,返回结果是-1vector<pthread_t> tids;//6.创建多线程for(int i=0;i<num;i++){//1.线程的idpthread_t tid;//2.线程的namechar *name= new char[128];snprintf(name,128,"thread-%d",i+1);pthread_create(&tid,nullptr,threadrun,/*线程的名字*/name);//3.保存所有信息的idtids.emplace_back(tid);}// //创建多线程的等待for(auto tid:tids){pthread_cancel(tid);//取消线程cout<<"cancel thread"void* result = nullptr;pthread_join(tid,&result);//线程被取消的后果是-1}return 0;
}

线程分离:

void* threadrun(void* args)//创建多线程需要的新线程函数
{pthread_detach(pthread_self());//自己要线程分离string name = static_cast<const char*>(args);while (true){cout << name << " is runing" << endl;sleep(1);}//exit(1)专门用来终止进程的,不能用来终止线程pthread_exit(args);//专门终止一个线程
}
const int num = 10;
//main函数结束,main thread结束,表示进程也就结束
int main()//线程的创建和等待
{//8.可以不join线程,让他执行完就退出//线程分离int pthread_detach(pthread_t thread)a. 一个线程被创建,默认是joinable的,必须要被join的.// b. 如果一个线程被分离,线程的工作状态分离状态,不须要/不能被join了. 依旧属于进程内部,但是不需要被等待了vector<pthread_t> tids;// //6.创建多线程for(int i=0;i<num;i++){//1.线程的idpthread_t tid;//2.线程的namechar *name= new char[128];snprintf(name,128,"thread-%d",i+1);pthread_create(&tid,nullptr,threadrun,/*线程的名字*/name);//3.保存所有信息的idtids.emplace_back(tid);}//主线程主动分离// for(auto tid:tids)// {//     pthread_detach(tid);//主线程分离新线程,新线程必须存在// }return 0;
}

给用户提供的线程id,不是内核中的lwp,而是pthread库里面维护的唯一值。

id就是在内存中的一个起始地址。

 多个线程能够看到的资源---共享资源。

线程封装:

#include <iostream>
#include <string>
#include <pthread.h>
#include<functional>
using namespace std;namespace ThreadModule {typedef void (*func_t)(const std::string& name); // 函数指针类型:返回值是void class Thread{public:void Excute(){std::cout << _name << " is running" << std::endl;_isrunning = true;_func(_name);_isrunning = false;}public:Thread(const std::string& name, func_t func) :_name(name), _func(func){std::cout << "create " << name << " done" << std::endl;}static void* ThreadRoutine(void* args)//这是类里面的,默认有this,和创建的主线程参数不匹配所以要加static;新线程都会执行该方法,{Thread* self = static_cast<Thread*>(args);//获得了当前对象self->Excute();//调用回调函数return nullptr;}bool Start(){int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, nullptr);//使用的是pthread库里面的if (n != 0){return false;}return true;}std::string Status()//线程启动了检测状态{if (_isrunning) return "running";else return "sleep";}std::string Name(){return _name;}void Stop(){if (!_isrunning){pthread_cancel(_tid);_isrunning = false;}}void Join(){pthread_join(_tid, nullptr);}~Thread(){ }private:string _name;pthread_t _tid;bool _isrunning;func_t _func;//线程要执行的回调函数};
}

线程互斥:

互斥锁:任何时刻只允许一个线程进行资源访问。

对临界资源进行保护本质就是对临界区的代码进行保护。

对所有资源进行保护本质是通过代码访问的,保护资源本质就是把访问资源的代码保护起来。

原子性:要么不做,要做就做完,没有中间状态(只有一条汇编语言)。

线程申请锁成功了,继续向后运行,申请所失败则被阻塞。

当线程申请所成功了,执行临界区代码期间被切换了,其他线程不能进入,因为没有释放锁。

 线程同步:

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免 饥饿问题。

条件变量:

条件变量接口代码的实现: 

//条件变量就是来唤醒线程的
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>const int num = 5;
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;//定义一个互斥锁
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;//定义一个条件变量void *Wait(void *args)
{std::string name = static_cast<const char *>(args);while (true){pthread_mutex_lock(&gmutex);pthread_cond_wait(&gcond, &gmutex /*?*/); // 这里就是线程等待的位置usleep(10000);std::cout << "I am : " << name << std::endl;pthread_mutex_unlock(&gmutex);}
}int main()
{pthread_t threads[num];for (int i = 0; i < num; i++){char *name = new char[1024];snprintf(name, 1024, "thread-%d", i + 1);pthread_create(threads + i, nullptr, Wait, (void *)name);//创建线程usleep(10000);}// 唤醒其他线程while (true){// pthread_cond_signal(&gcond);//唤醒一个线程pthread_cond_broadcast(&gcond);//唤醒所有线程std::cout << "唤醒一个线程...." << std::endl;sleep(2);}for (int i = 0; i < num; i++)//线程等待{pthread_join(threads[i], nullptr);}return 0;
}

 生产消费模型:

阻塞队列:

 阻塞队列(BlockingQueue)是⼀种常⽤于实现⽣产者和消费者模型的数据结构。其与 普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放⼊了元 素;当队列满时,往队列⾥存放元素的操作也会被阻塞,直到有元素被从队列中取出。

321原则:1:一个交易场所(特定数据结构形式存在的一段内存空间),2:2中角色(生产角色,消费角色即生产线程,消费线程)3:三种关系即生产和生产(互斥关系),消费和消费(互斥关系),生产和消费(互斥,同步关系)之间的关系。

生产消费模型的模拟实现:

“BlockQueue.hpp”#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>const static int defaultcap = 5;template <typename T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap = defaultcap) : _max_cap(cap){pthread_mutex_init(&_mutex, nullptr);//锁的初始化pthread_cond_init(&_p_cond, nullptr);//条件变量初始化pthread_cond_init(&_c_cond, nullptr);}void Pop(T* out){pthread_mutex_lock(&_mutex);// 当一个生产者两个消费者时while (IsEmpty()) // while可以保证代码的鲁棒性(健壮性){// 添加尚未满足,但是线程被异常唤醒的情况,叫做伪唤醒!pthread_cond_wait(&_c_cond, &_mutex); //一个消费者竞争成功会拿走数据,另一个消费者在锁这里等待,当被释放时,向下运行没有数据了,绕过了判断队列为空的条件,所以用while不用if}// 1. 没有空 || 2. 被唤醒了*out = _block_queue.front();_block_queue.pop();// if(_block_queue.size() > hight_water)//     pthread_cond_signal(&_p_cond);pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond);}// 一个生产者void Equeue(const T& in){pthread_mutex_lock(&_mutex);while (IsFull()) {// 满了,生产者不能生产,必须等// 被调用的时候:除了让自己继续排队等待,还会自己释放传入的锁// 函数返回的时候,不就还在临界区了!// 返回时:必须先参与锁的竞争,重新加上锁,该函数才会返回!pthread_cond_wait(&_p_cond, &_mutex);}// 1. 没有满 || 2. 被唤醒了_block_queue.push(in); // 生产到阻塞队列pthread_mutex_unlock(&_mutex);// 让消费者消费pthread_cond_signal(&_c_cond); // 唤醒}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _block_queue; // 临界资源  定义一个队列int _max_cap;//队列的容量pthread_mutex_t _mutex;//定义一个锁pthread_cond_t _p_cond; // 生产者条件变量pthread_cond_t _c_cond; // 消费者条件变量
};
#include<iostream>
#include<functional>// typedef std::function<void()> task_t;
using task_t = std::function<void()>;//定义一个函数类型,返回值是void,参数为空void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}void* Consumer(void* args)
{BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);while (true){// 1. 获取数据task_t t;bq->Pop(&t);// 2. 处理数据// t.Excute();t();// std::cout << "Consumer -> " << t.result()  << std::endl;}
}void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);while (true){bq->Equeue(Download);std::cout << "Productor -> Download" << std::endl;sleep(1);}
}int main()
{BlockQueue<task_t>* bq = new BlockQueue<task_t>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}

信号量:

信号量系统调用:

环形队列--生产消费模型:

RingQueue.hpp#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <semaphore.h>template <typename T>
class RingQueue
{
private:void P(sem_t& s)//p操作{sem_wait(&s);}void V(sem_t& s)//v操作{sem_post(&s);}
public:RingQueue(int max_cap): _ringqueue(max_cap), _max_cap(max_cap), _c_step(0), _p_step(0){sem_init(&_data_sem, 0, 0);sem_init(&_space_sem, 0, max_cap);//一开始空间信号量的值是容量的最大值pthread_mutex_init(&_c_mutex, nullptr);pthread_mutex_init(&_p_mutex, nullptr);}void Push(const T& in) //生产者{// 信号量:是一个计数器,是资源的预订机制。// 预订在外部,可以不判断资源是否满足,就可以知道内部资源的情况!P(_space_sem); // 信号量这里,对资源进行使用,申请,不判断一下条件是否满足???因为信号量本身就是判断条件!pthread_mutex_lock(&_p_mutex); _ringqueue[_p_step] = in;_p_step++;_p_step %= _max_cap;pthread_mutex_unlock(&_p_mutex);V(_data_sem);}void Pop(T* out) // 消费{P(_data_sem);//拿数据,所以数据减少pthread_mutex_lock(&_c_mutex); //?*out = _ringqueue[_c_step];_c_step++;_c_step %= _max_cap;pthread_mutex_unlock(&_c_mutex);V(_space_sem);}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);pthread_mutex_destroy(&_c_mutex);pthread_mutex_destroy(&_p_mutex);}
private:std::vector<T> _ringqueue;//用数组管理队列int _max_cap;//数组容量int _c_step;//消费者数组下标int _p_step;//生产者数组下标sem_t _data_sem; // 数据信号量,消费者关心sem_t _space_sem; // 空间信号量,生产者关心pthread_mutex_t _c_mutex;//互斥锁pthread_mutex_t _p_mutex;
};
#include "RingQueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>void* Consumer(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task> *>(args);//Task是一个类while (true){Task t;// 1. 消费rq->Pop(&t);// 2. 处理数据t();//Task有一个重载函数()
}
void* Productor(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task> *>(args);while (true){sleep(1);// 1. 构造数据int x = rand() % 10 + 1; //[1, 10]usleep(x * 1000);int y = rand() % 10 + 1;Task t(x, y);// 2. 生产rq->Push(t);}
}int main()
{srand(time(nullptr) ^ getpid());RingQueue<Task>* rq = new RingQueue<Task>(5);pthread_t c1,  p1;pthread_create(&c1, nullptr, Consumer, rq);pthread_create(&p1, nullptr, Productor, rq);pthread_join(c1, nullptr);pthread_join(p1, nullptr);return 0;
}


 

相关文章:

  • Hive优化详细讲解
  • AI与大数据如何驱动工业品电商平台的智能决策?
  • WRF-Hydro分布式水文模型:洪水预报、水资源管理与规划、生态水文研究、气候变化影响评估、流域综合管理、水电工程规划与运行
  • C++问题:深拷贝和浅拷贝
  • 华为网路设备学习-25(路由器OSPF - 特性专题 二)
  • 【图像处理入门】11. 深度学习初探:从CNN到GAN的视觉智能之旅
  • 超高速10G采集卡
  • 《记者观察》期刊投稿
  • ③-1实现 FastAdmin 默认开启通用搜索功能的方法
  • python3控制流程
  • 教师办工专用 资源包|课件+手抄报+PPT模板+常用表格 PDF格式93GB
  • Java八股文——计算机网络「网络攻击篇」
  • 数据结构 6(算法)
  • 计算机网络 网络层:数据平面(一)
  • Samba
  • Spark 在小众日常场景中的实战应用:从小店数据到社区活动
  • 对gateway和nocas的理解
  • 如何利用 Java 爬虫按关键字搜索 Amazon 商品:实战指南
  • 惠普HP Laser 105a打印机信息
  • Qt数据库
  • 关键词优化搜索引擎/优化网哪个牌子好
  • 新公司怎么建立自己的网站/会计培训班初级费用
  • 中介网站怎么做/如何进行网络推广
  • 世界十大网站排名/营销型网站更受用户欢迎的原因是
  • 大气建站工作室网站源码/青岛seo优化
  • 网站域名到期后不续费会怎样/seo官网优化