Linux:信号和线程

发布于:2025-06-20 ⋅ 阅读:(22) ⋅ 点赞:(0)

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

自定义的方法如下所示:

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)取消线程前提是线程要存在,返回结果是-1
    
     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)
     {
        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.线程的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)
    // {
    //     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;
}


 


网站公告

今日签到

点亮在社区的每一天
去签到