【Linux】信号量

信号量

  • 一、POSIX信号量
    • 1、信号量的原理
    • 2、信号量的概念
      • (1)PV操作必须是原子操作
      • (2)申请信号量失败被挂起等待
    • 3、信号量函数
    • 4、销毁信号量
    • 5、等待信号量(申请信号量)
    • 6、发布信号量(释放信号量)
  • 二、二元信号量模拟实现互斥功能
    • 1、负数情况
    • 2、引入二元信号量
  • 三、基于环形队列的生产消费模型
    • 1、空间资源和数据资源
      • (1)生产者和消费者关注的不同资源
      • (2)blank_sem和data_sem的初始值设置
      • (3)生产者和消费者申请和释放资源
        • i、生产者申请空间资源,释放数据资源
        • ii、消费者申请数据资源,释放空间资源
    • 2、两个规则
      • (1)生产者和消费者不能对同一个位置进行访问
      • (2)无论是生产者还是消费者,都不应该将对方套一个圈以上
    • 3、利用代码进行讲解
      • (1)消费者和生产者步调一致
      • (2)生产者生产的快,消费者消费的慢
      • (3)生产者生产的慢,消费者消费的快
  • 四、信号量保护环形队列的原理


一、POSIX信号量

1、信号量的原理

  • 我们将可能会被多个执行流同时访问的资源叫做临界资源,临界资源需要进行保护否则会出现数据不一致等问题。
  • 当我们仅用一个互斥锁对临界资源进行保护时,相当于我们将这块临界资源看作一个整体,同一时刻只允许一个执行流对这块临界资源进行访问
  • 但实际我们可以将这块临界资源再分割为多个区域,当多个执行流需要访问临界资源时,如果这些执行流访问的是临界资源的不同区域,那么我们可以让这些执行流同时访问临界资源的不同区域,此时不会出现数据不一致等问题

2、信号量的概念

信号量(信号灯)本质是一个计数器,是描述临界资源中资源数目的计数器,信号量能够更细粒度的对临界资源进行管理。

每个执行流在进入临界区之前都应该先申请信号量,申请成功就有了操作特点的临界资源的权限,当操作完毕后就应该释放信号量。

在这里插入图片描述

信号量的PV操作:

  • P操作:我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一,因此P操作的本质就是让计数器减一
  • V操作:我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作的本质就是让计数器加一

(1)PV操作必须是原子操作

多个执行流为了访问临界资源会竞争式的申请信号量,因此信号量是会被多个执行流同时访问的,也就是说信号量本质也是临界资源
但信号量本质就是用于保护临界资源的,我们不可能再用信号量去保护信号量,所以信号量的PV操作必须是原子操作

(2)申请信号量失败被挂起等待

当执行流在申请信号量时,可能此时信号量的值为0,也就是说信号量描述的临界资源已经全部被申请了,此时该执行流就应该在该信号量的等待队列当中进行等待,直到有信号量被释放时再被唤醒。信号量的本质是计数器,但不意味着只有计数器,信号量还包括一个等待队列。

3、信号量函数

初始化信号量的函数叫做sem_init,该函数的函数原型如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明

  • sem:需要初始化的信号量。
  • pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
  • value:信号量的初始值(计数器的初始值)。

返回值

初始化信号量成功返回0,失败返回-1。

注意: POSIX信号量和System V信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的,但POSIX信号量可以用于线程间同步。

4、销毁信号量

销毁信号量的函数叫做sem_destroy,该函数的函数原型如下:

int sem_destroy(sem_t *sem);

参数说明

  • sem:需要销毁的信号量。

返回值说明
销毁信号量成功返回0,失败返回-1。

5、等待信号量(申请信号量)

等待信号量的函数叫做sem_wait,该函数的函数原型如下:

int sem_wait(sem_t *sem);

参数说明

  • sem:需要等待的信号量。

返回值说明

  • 等待信号量成功返回0,信号量的值减一。
  • 等待信号量失败返回-1,信号量的值保持不变。

6、发布信号量(释放信号量)

发布信号量的函数叫做sem_post,该函数的函数原型如下:

int sem_post(sem_t *sem);

参数说明

  • sem:需要发布的信号量。

返回值说明

  • 发布信号量成功返回0,信号量的值加一。
  • 发布信号量失败返回-1,信号量的值保持不变。

二、二元信号量模拟实现互斥功能

1、负数情况

信号量本质是一个计数器,如果将信号量的初始值设置为1,那么此时该信号量叫做二元信号量。

信号量的初始值为1,说明信号量所描述的临界资源只有一份,此时信号量的作用基本等价于互斥锁

我们不知道还记得之间写的最初的抢票逻辑吗?我们创建四个新线程,四个新线程实现抢票,但最终是出现负数的情况,我们再看一眼下面的代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
// 临界资源1000张票
int tickets = 1000;
void* Routine(void* arg)
{const char* name = (char*)arg;// 抢票while(1){if(tickets > 0){// 抢票usleep(10000);printf("[%s] get a ticket...left tickets#%d\n", name, --tickets);}else{break;}}cout << name << "exit..." << endl;pthread_exit((void*)0);
}
int main()
{pthread_t tid1, tid2, tid3, tid4;// 创建四个新线程pthread_create(&tid1, NULL, Routine, (void*)"thread 1");pthread_create(&tid2, NULL, Routine, (void*)"thread 2");pthread_create(&tid3, NULL, Routine, (void*)"thread 3");pthread_create(&tid4, NULL, Routine, (void*)"thread 4");// 退出pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);pthread_join(tid4, NULL);return 0;
}

我们发现是有负数的,这显然不符合我们预期的。
在这里插入图片描述

2、引入二元信号量

下面我们在抢票逻辑当中加入二元信号量,让每个线程在访问全局变量tickets之前先申请信号量,访问完毕后再释放信号量,此时二元信号量就达到了互斥的效果。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <string>
// 实现一个Sem类函数
class Sem
{
private:sem_t _sem;
public:// 构造函数Sem(int num){sem_init(&_sem, 0, num);}// 析构函数~Sem(){sem_destroy(&_sem);}// P操作void P(){sem_wait(&_sem);}// V操作void V(){sem_post(&_sem);}
};
// 二原信号量
Sem sem(1);
int tickets = 1000;
void* TicketGrabbge(void* arg)
{// 抢票std::string Name = (char*)arg;while(1){sem.P(); // 等待信号量if(tickets > 0){// 出票usleep(10000);std::cout << "[" << Name << "]" << "get a tickets#" << --tickets << std::endl;sem.V(); // 发布信号量}else{sem.V(); // 发布信号量break;}}std::cout << Name << "quit..." << std::endl;pthread_exit((void*)0);
}
int main()
{pthread_t tid1, tid2, tid3, tid4;// 创建线程pthread_create(&tid1, nullptr, TicketGrabbge, (void*)"thread 1");pthread_create(&tid2, nullptr, TicketGrabbge, (void*)"thread 2");pthread_create(&tid3, nullptr, TicketGrabbge, (void*)"thread 3");pthread_create(&tid4, nullptr, TicketGrabbge, (void*)"thread 4");// 线程等待pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);pthread_join(tid4, nullptr);return 0;
}

运行代码后就不会出现剩余票数为负的情况了,因为此时同一时刻只会有一个执行流对全局变量tickets进行访问,不会出现数据不一致的问题。
在这里插入图片描述

三、基于环形队列的生产消费模型

在这里插入图片描述

1、空间资源和数据资源

(1)生产者和消费者关注的不同资源

生产者关注的是空间资源,消费者关注的是数据资源

对于生产者和消费者来说,它们关注的资源是不同的:

  • 生产者关注的是环形队列当中是否有空间(blank),只要有空间生产者就可以进行生产。
  • 消费者关注的是环形队列当中是否有数据(data),只要有数据消费者就可以进行消费。

(2)blank_sem和data_sem的初始值设置

现在我们用信号量来描述环形队列当中的空间资源(blank_sem)和数据资源(data_sem),在我们初始信号量时给它们设置的初始值是不同的:

  • blank_sem的初始值我们应该设置为环形队列的容量,因为刚开始时环形队列当中全是空间。
  • data_sem的初始值我们应该设置为0,因为刚开始时环形队列当中没有数据。

(3)生产者和消费者申请和释放资源

i、生产者申请空间资源,释放数据资源

对于生产者来说,生产者每次生产数据前都需要先申请blank_sem

  • 如果blank_sem的值不为0,则信号量申请成功,此时生产者可以进行生产操作。
  • 如果blank_sem的值为0,则信号量申请失败,此时生产者需要在blank_sem的等待队列下进行阻塞等待,直到环形队列当中有新的空间后再被唤醒。

当生产者生产完数据后,应该释放data_sem

  • 虽然生产者在进行生产前是对blank_sem进行的P操作,但是当生产者生产完数据,应该对data_sem进行V操作而不是blank_sem
  • 生产者在生产数据前申请到的是blank位置,当生产者生产完数据后,该位置当中存储的是生产者生产的数据,在该数据被消费者消费之前,该位置不再是blank位置,而应该是data位置
  • 当生产者生产完数据后,意味着环形队列当中多了一个data位置,因此我们应该对data_sem进行V操作
ii、消费者申请数据资源,释放空间资源

对于消费者来说,消费者每次消费数据前都需要先申请data_sem

  • 如果data_sem的值不为0,则信号量申请成功,此时消费者可以进行消费操作。
  • 如果data_sem的值为0,则信号量申请失败,此时消费者需要在data_sem的等待队列下进行阻塞等待,直到环形队列当中有新的数据后再被唤醒。

当消费者消费完数据后,应该释放blank_sem

  • 虽然消费者在进行消费前是对data_sem进行的P操作,但是当消费者消费完数据,应该对blank_sem进行V操作而不是data_sem
  • 消费者在消费数据前申请到的是data位置,当消费者消费完数据后,该位置当中的数据已经被消费过了,再次被消费就没有意义了,为了让生产者后续可以在该位置生产新的数据,我们应该将该位置算作blank位置,而不是data位置
  • 当消费者消费完数据后,意味着环形队列当中多了一个blank位置,因此我们应该对blank_sem进行V操作

2、两个规则

(1)生产者和消费者不能对同一个位置进行访问

  • 如果生产者和消费者访问的是环形队列当中的同一个位置,那么此时生产者和消费者就相当于同时对这一块临界资源进行了访问,这当然是不允许的。
  • 而如果生产者和消费者访问的是环形队列当中的不同位置,那么此时生产者和消费者是可以同时进行生产和消费的,此时不会出现数据不一致等问题。

在这里插入图片描述

(2)无论是生产者还是消费者,都不应该将对方套一个圈以上

  • 生产者从消费者的位置开始一直按顺时针方向进行生产,如果生产者生产的速度比消费者消费的速度快,那么当生产者绕着消费者生产了一圈数据后再次遇到消费者,此时生产者就不应该再继续生产了,因为再生产就会覆盖还未被消费者消费的数据。
  • 同理,消费者从生产者的位置开始一直按顺时针方向进行消费,如果消费者消费的速度比生产者生产的速度快,那么当消费者绕着生产者消费了一圈数据后再次遇到生产者,此时消费者就不应该再继续消费了,因为再消费就会消费到缓冲区中保存的废弃数据。

在这里插入图片描述

3、利用代码进行讲解

signal.hpp:

#pragma once
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>
#define NUM 10
template<class T>
class RingQueue
{
private:std::vector<T> _q; // 循环队列int _capacity;     // 循环队列的容量int _p_pos;        // 生产位置int _c_pos;        // 消费位置sem_t _blank_sem;  // 描述空间资源sem_t _data_sem;   // 描述数据资源
private:// P操作 -- 等待void P(sem_t& s){sem_wait(&s);}// V操作 -- 传递void V(sem_t& s){sem_post(&s);}
public:// 构造函数RingQueue(int cap = NUM):_capacity(cap),_p_pos(0),_c_pos(0){_q.resize(_capacity);sem_init(&_blank_sem, 0, _capacity); // blank_sem初始值设置为环形队列的容量sem_init(&_data_sem, 0, 0); // _data_sem初始值设置为0}// 析构函数~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}// 生产者 -- 向环形队列中插入元素void Push(const T& data){P(_blank_sem); // 向空间资源加锁!_q[_p_pos] = data; // 环形队列中的相对应位置用data填入V(_data_sem); // 向数据资源解锁!即生产资源// 更新下一次插入的位置_p_pos++;_p_pos %= _capacity;}// 消费者void Pop(T& data){P(_data_sem); // 向数据资源加锁!data = _q[_c_pos]; // 拿到消费位置的数据V(_blank_sem); // 空间资源的解锁,即开始将空间资源进行增加空间// 更新下一次弹出的位置_c_pos++;_c_pos %= _capacity;}
};
  • 当不设置环形队列的大小时,我们默认将环形队列的容量上限设置为10
  • 代码中的RingQueue是用vector实现的,生产者每次生产的数据放到vector下标为p_pos的位置消费者每次消费的数据来源于vector下标为c_pos的位置
  • 生产者每次生产数据后p_pos都会进行++,标记下一次生产数据的存放位置,++后的下标会与环形队列的容量进行取模运算,实现“环形”的效果。
  • 消费者每次消费数据后c_pos都会进行++,标记下一次消费数据的来源位置,++后的下标会与环形队列的容量进行取模运算,实现“环形”的效果。
  • p_pos只会由生产者线程进行更新,c_pos只会由消费者线程进行更新,对这两个变量访问时不需要进行保护,因此代码中将p_pos和c_pos的更新放到了V操作之后,就是为了尽量减少临界区的代码

我们这里设置两个线程,一个是消费者线程另一个是生产者线程,生产者负责生产数据到环形队列中,消费者负责在环形队列中获取数据:
main.cc:

#include "signal.hpp"void* Productor(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){sleep(1);int data = rand() % 100 + 1;Name->Push(data);std::cout << "Productor###" << data << std::endl;}
}void* Consumer(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){sleep(1);int data = 0;Name->Pop(data);std::cout << "Consumer###" << data << std::endl;}
}int main()
{srand((unsigned int)time(nullptr));pthread_t tid1, tid2;// 创造两个新线程RingQueue<int>* rq = new RingQueue<int>;pthread_create(&tid1, nullptr, Productor, rq);pthread_create(&tid2, nullptr, Consumer, rq);// 线程等待pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);delete rq; // 防止内存泄漏return 0;
}

说明

  • 环形队列要让生产者线程向队列中Push数据让消费者线程从队列中Pop数据,因此这个环形队列必须要让这两个线程同时看到,所以我们在创建生产者线程和消费者线程时,需要将环形队列作为线程执行例程的参数进行传入。
  • 代码中生产者生产数据就是将获取到的随机数Push到环形队列,而消费者就是从环形队列Pop数据,为了便于观察,我们可以将生产者生产的数据和消费者消费的数据进行打印输出。

(1)消费者和生产者步调一致

我们上面的代码就是消费者和消费者步调一致,我们进行运行代码并进行打印一看:

在这里插入图片描述

(2)生产者生产的快,消费者消费的慢

生产者不停的进行生产,而消费者每隔一秒进行消费。

void* Productor(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){int data = rand() % 100 + 1;Name->Push(data);std::cout << "Productor###" << data << std::endl;}
}void* Consumer(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){sleep(1);int data = 0;Name->Pop(data);std::cout << "Consumer###" << data << std::endl;}
}

此时由于生产者生产的很快,运行代码后一瞬间生产者就将环形队列打满了,此时生产者想要再进行生产,但空间资源已经满了,于是生产者只能在blank_sem的等待队列下进行阻塞等待,直到由消费者消费完一个数据后对blank_sem进行了V操作,生产者才会被唤醒进而继续进行生产。
但由于生产者的生产速度很快,生产者生产完一个数据后又会进行等待,因此后续生产者和消费者的步调又变成一致的了。
在这里插入图片描述

(3)生产者生产的慢,消费者消费的快

生产者每隔一秒进行生产,而消费者不停的进行消费。

void* Productor(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){sleep(1);int data = rand() % 100 + 1;Name->Push(data);std::cout << "Productor###" << data << std::endl;}
}void* Consumer(void* arg)
{RingQueue<int>* Name = (RingQueue<int>*)arg;while(1){int data = 0;Name->Pop(data);std::cout << "Consumer###" << data << std::endl;}
}

虽然消费者消费的很快,但一开始环形队列当中的数据资源为0,因此消费者只能在data_sem的等待队列下进行阻塞等待,直到生产者生产完一个数据后对data_sem进行了V操作,消费者才会被唤醒进而进行消费。
但由于消费者的消费速度很快,消费者消费完一个数据后又会进行等待,因此后续生产者和消费者的步调又变成一致的了。
在这里插入图片描述

四、信号量保护环形队列的原理

在blank_sem和data_sem两个信号量的保护后,该环形队列中不可能会出现数据不一致的问题。

因为只有当生产者和消费者指向同一个位置并访问时,才会导致数据不一致的问题,而此时生产者和消费者在对环形队列进行写入或读取数据时,只有两种情况会指向同一个位置:

  • 环形队列为空时。
  • 环形队列为满时。

但是在这两种情况下,生产者和消费者不会同时对环形队列进行访问:

  • 当环形队列为空的时,消费者一定不能进行消费,因为此时数据资源为0。
  • 当环形队列为满的时,生产者一定不能进行生产,因为此时空间资源为0。

当环形队列为空和满时,我们已经通过信号量保证了生产者和消费者的串行化过程。而除了这两种情况之外,生产者和消费者指向的都不是同一个位置,因此该环形队列当中不可能会出现数据不一致的问题。并且大部分情况下生产者和消费者指向并不是同一个位置,因此大部分情况下该环形队列可以让生产者和消费者并发的执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/661277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20240131在WIN10下配置whisper

20240131在WIN10下配置whisper 2024/1/31 18:25 首先你要有一张NVIDIA的显卡&#xff0c;比如我用的PDD拼多多的二手GTX1080显卡。【并且极其可能是矿卡&#xff01;】800&#xffe5; 2、请正确安装好NVIDIA最新的545版本的驱动程序和CUDA。 2、安装Torch 3、配置whisper http…

仰暮计划|“从米票、肉票、糖果票到肥皂票、煤票、棉花票等,生活里头的方方面面都能用粮票买到”

口述人&#xff1a;牛翠英(女) 整理人&#xff1a;霍芝冉 口述人基本信息&#xff1a;现68岁&#xff0c;河南省安阳市北关区霍家村人&#xff0c;现居河南安阳市区。 奶奶一生辛劳&#xff0c;操持家务&#xff1b;亲眼见证了时代变迁&#xff0c;社会发展&#xff0c;…

docker笔记整理

Docker 安装 添加yum源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 安装docker yum -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin 启动docker systemctl start docker 查看docker状态 s…

09. 异常处理

目录 1、前言 2、常见的异常 3、异常处理try...except...finally 4、异常信息解读 5、raise 6、自定义异常 7、小结 1、前言 在编程中&#xff0c;异常&#xff08;Exception&#xff09;是程序在运行期间检测到的错误或异常状况。当程序执行过程中发生了一些无法继续执…

C languange DGEQRF 示例,link liblapack.a

1.示例源码 #include <stdio.h>int min(int m, int n){ return m<n? m:n;}void print_matrix(double* A, int m, int n, int lda) {for (int i 0; i < m; i){for (int j 0; j < n; j){//printf("%7.4f ", A[i j*lda]);printf("%7.4f, &quo…

创建表与删除表(六)

表的基本操作&#xff08;六&#xff09; 一、创建表 1.1 使用DDL语句创建表 CREATE TABLE 表名(列名 类型,列名 类型......); 示例&#xff1a; 创建一个 employees 表包含雇员 ID &#xff0c;雇员名字&#xff0c;雇员薪水。 create table employees(employee_id int,em…

从创新者到引领者:探索第四范式的AI之旅

大数据产业创新服务媒体 ——聚焦数据 改变商业 如今&#xff0c;人工智能已成为改变世界、驱动各行各业变革的核心源动力。在国内&#xff0c;有一些公司已走在前列&#xff0c;其中就包括北京第四范式智能技术股份有限公司&#xff0c;在AI这个赛道&#xff0c;他是一名创新…

Patch2QL:开源供应链漏洞挖掘和检测的新方向

背景 开源生态的上下游中&#xff0c;漏洞可能存在多种成因有渊源的其它缺陷&#xff0c;统称为“同源漏洞”&#xff0c;典型如&#xff1a; 上游代码复用缺陷。开源贡献者在实现功能相似的模块时&#xff0c;常复用已有模块代码或逻辑&#xff1b;当其中某个模块发现漏洞后…

java:java反编译工具--jd-gui

JD-GUI是一款反编译软件&#xff0c;JD分为JD-GUI、JD-Eclipse两种运行方式&#xff0c;JD-GUI是以单独的程序的方式运行&#xff0c;JD-Eclipse则是以一个Eclipse插件的方式运行。 官方下载地址&#xff1a; https://github.com/java-decompiler/jd-gui/releases 我这边下载…

Unity Shader 滚动进度条效果

Unity Shader 滚动进度条效果 前言项目场景布置导入图片修改场景设置修改图片尺寸即可调整进度 ASE连线 前言 UI要实现一个滚动进度&#xff0c;于是使用Shader制作一个。 项目 场景布置 导入图片 修改一下导入图片的格式&#xff0c;这样才能循环起来 WrapMode改为Repea…

正点原子--STM32中断系统学习笔记(1)

1、什么是中断&#xff1f; 原子哥给出的概念是这样的&#xff1a;打断CPU正常执行的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断。 当发生中断时&#xff0c;当前执行的程序会被暂时中止&#xff0c;进而进入中断处理函…

Node: opensslErrorStack: [ ‘error:03000086:digital envelope routines::initialization error‘ ]异常处理

目录 一、问题描述二、问题分析三、解决方案方案一&#xff1a;你可以按照以下步骤来删除 NODE_OPTIONS 环境变量中的 --openssl-legacy-provider 选项&#xff1a;方案二&#xff1a;在package.json更改scripts方案三&#xff1a;降级 Node.js 版本 在进行前端项目开发时&…

Linux+服务器后台运行程序

在Linux服务器直接运行程序&#xff0c;程序运行的时间较长&#xff0c;程序经常会因为网络连接问题异常终止&#xff0c;一直盯着程序运行又费时费力&#xff0c;这时后台运行程序是更好的解决方式。But&#xff0c;如果服务器重启了&#xff0c;那所有进程都断掉了&#xff0…

关于v8垃圾回收机制以及与其相关联的知识点--还没整理版本

对于值类型b来说&#xff0c;就直接释放了其占用的内存&#xff0c;对于引用类型obj来说&#xff0c;销毁的只是变量obj对堆内存地址 1001 的引用&#xff0c;obj的值 { c: 3 } 依然存在于堆内存中。那么堆内存中的变量如何进行回收呢&#xff1f; V8的垃圾回收策略主要是基于…

【Java 数据结构】栈和队列

栈和队列 1. 栈(Stack)1.1 概念1.2 栈的使用1.3 栈的模拟实现1.4 栈的应用场景1.5 概念区分 2. 队列(Queue)2.1 概念2.2 队列的使用2.3 队列模拟实现2.4 循环队列 3. 双端队列 (Deque)4. 面试题 1. 栈(Stack) 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在…

自然语言处理,基于预训练语言模型的方法,车万翔,引言部分

文章目录 自然语言处理应用任务1. 信息抽取2. 情感分析3. 问答系统4. 机器翻译5. 对话系统 自然语言处理应用任务 1. 信息抽取 信息抽取&#xff08;Information Extraction, IE&#xff09;&#xff0c;是从非结构化的文本中&#xff0c;抽取出结构化信息的过程&#xff0c;…

C++ 单一附合导线平差程序

一、以下图附合导线为例&#xff0c;图形如下&#xff1a; 二、第一步&#xff0c;读取测量数据&#xff0c;读取界面设计如下&#xff1a; 读取数据文本文件格式如下&#xff1a; &#xff08;1&#xff09;已知点坐标数据格式&#xff1a; &#xff08;2&#xff09;角度观测…

【C++杂货铺】详解类和对象 [下]

个人博客&#xff1a;代码菌-CSDN博客 专栏&#xff1a;C杂货铺_代码菌的博客-CSDN博客 目录 &#x1f308;前言&#x1f308; &#x1f4c1; 初始化列表&#xff08;灰常重要&#xff09; &#x1f4c2; 引入 &#x1f4c2; 概念 &#x1f4c2; 特性 &#x1f4c1; 拓展构…

51单片机编程应用(C语言):数码管

目录 1.数码管原理 一位数码管引脚定义&#xff1a; 四位一体数码管&#xff1a; 多个数码管同时显示不同数字 51单片机的数码管的原理图 51单片机实现静态显示和动态显示 静态显示&#xff1a; 动态显示&#xff1a; 1.数码管原理 一位数码管引脚定义&#xff1a; 数码…

外包干了10个月,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…