【Linux信号量】

2023-05-16

Linux信号量

  • POSIX信号量
    • 信号量的原理
    • 信号量的概念
    • 信号量函数
  • 二元信号量模拟实现互斥功能
  • 基于环形队列的生产消费模型
    • 空间资源和数据资源
    • 生产者和消费者申请和释放资源
    • 必须遵守的两个规则
    • 信号量保护环形队列的原理

POSIX信号量

信号量的原理

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

信号量的概念

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

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

信号量的PV操作:

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

PV操作必须是原子操作

多个执行流为了访问临界资源会竞争式的申请信号量,因此信号量是会被多个执行流同时访问的,也就是说信号量本质也是临界资源。

但信号量本质就是用于保护临界资源的,我们不可能再用信号量去保护信号量,所以信号量的PV操作必须是原子操作。

注意: 内存当中变量的++、–操作并不是原子操作,因此信号量不可能只是简单的对一个全局变量进行++、–操作。

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

当执行流在申请信号量时,可能此时信号量的值为0,也就是说信号量描述的临界资源已经全部被申请了,此时该执行流就应该在该信号量的等待队列当中进行等待,直到有信号量被释放时再被唤醒。

注意: 信号量的本质是计数器,但不意味着只有计数器,信号量还包括一个等待队列。

信号量函数

初始化信号量

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

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

参数说明:

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

返回值说明:

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

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

销毁信号量

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

int sem_destroy(sem_t *sem);

参数说明:

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

返回值说明:

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

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

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

int sem_wait(sem_t *sem);

参数说明:

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

返回值说明:

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

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

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

int sem_post(sem_t *sem);

参数说明:

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

返回值说明:

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

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

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

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

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

在这里插入图片描述

空间资源和数据资源

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

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

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

blank_sem和data_sem的初始值设置

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

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

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

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

对于生产者来说,生产者每次生产数据前都需要先申请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操作。

消费者申请数据资源,释放空间资源

对于消费者来说,消费者每次消费数据前都需要先申请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操作。

必须遵守的两个规则

在基于环形队列的生产者和消费者模型当中,生产者和消费者必须遵守如下两个规则。

第一个规则:生产者和消费者不能对同一个位置进行访问。

生产者和消费者在访问环形队列时:

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

第二个规则:无论是生产者还是消费者,都不应该将对方套一个圈以上。

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

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>

#define NUM 8

template<class T>
class RingQueue
{
private:
	//P操作
	void P(sem_t& s)
	{
		sem_wait(&s);
	}
	//V操作
	void V(sem_t& s)
	{
	    sem_post(&s);
	}
public:
	RingQueue(int cap = NUM)
		: _cap(cap), _p_pos(0), _c_pos(0)
	{
		_q.resize(_cap);
		sem_init(&_blank_sem, 0, _cap); //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;
		V(_data_sem); //生产

		//更新下一次生产的位置
		_p_pos++;
		_p_pos %= _cap;
	}
	//从环形队列获取数据(消费者调用)
	void Pop(T& data)
	{
		P(_data_sem); //消费者关注数据资源
		data = _q[_c_pos];
		V(_blank_sem);

		//更新下一次消费的位置
		_c_pos++;
		_c_pos %= _cap;
	}
private:
	std::vector<T> _q; //环形队列
	int _cap; //环形队列的容量上限
	int _p_pos; //生产位置
	int _c_pos; //消费位置
	sem_t _blank_sem; //描述空间资源
	sem_t _data_sem; //描述数据资源
};

相关说明:

  • 当不设置环形队列的大小时,我们默认将环形队列的容量上限设置为8。
  • 代码中的RingQueue是用vector实现的,生产者每次生产的数据放到vector下标为p_pos的位置,消费者每次消费的数据来源于vector下标为c_pos的位置。
  • 生产者每次生产数据后p_pos都会进行++,标记下一次生产数据的存放位置,++后的下标会与环形队列的容量进行取模运算,实现“环形”的效果。
  • 消费者每次消费数据后c_pos都会进行++,标记下一次消费数据的来源位置,++后的下标会与环形队列的容量进行取模运算,实现“环形”的效果。
  • p_pos只会由生产者线程进行更新,c_pos只会由消费者线程进行更新,对这两个变量访问时不需要进行保护,因此代码中将p_pos和c_pos的更新放到了V操作之后,就是为了尽量减少临界区的代码。

为了方便理解,我们这里实现单生产者、单消费者的生产者消费者模型。于是在主函数我们就只需要创建一个生产者线程和一个消费者线程,生产者线程不断生产数据放入环形队列,消费者线程不断从环形队列里取出数据进行消费。

#include "RingQueue.hpp"

void* Producer(void* arg)
{
	RingQueue<int>* rq = (RingQueue<int>*)arg;
	while (true){
		sleep(1);
		int data = rand() % 100 + 1;
		rq->Push(data);
		std::cout << "Producer: " << data << std::endl;
	}
}
void* Consumer(void* arg)
{
	RingQueue<int>* rq = (RingQueue<int>*)arg;
	while (true){
		sleep(1);
		int data = 0;
		rq->Pop(data);
		std::cout << "Consumer: " << data << std::endl;
	}
}
int main()
{
	srand((unsigned int)time(nullptr));
	pthread_t producer, consumer;
	RingQueue<int>* rq = new RingQueue<int>;
	pthread_create(&producer, nullptr, Producer, rq);
	pthread_create(&consumer, nullptr, Consumer, rq);
	
	pthread_join(producer, nullptr);
	pthread_join(consumer, nullptr);
	delete rq;
	return 0;
}

相关说明:

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

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

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

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

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

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux信号量】 的相关文章

  • 亚马逊 Linux - 安装 openjdk-debuginfo?

    我试图使用jstack在 ec2 实例上amazon linux 所以我安装了openjdk devel包裹 sudo yum install java 1 7 0 openjdk devel x86 64 但是 jstack 引发了异常j
  • Apache LOG:子进程 pid xxxx 退出信号分段错误 (11)

    Apache PHP Mysql Linux 注意 子进程 pid 23145 退出信号分段错误 11 tmp 中可能存在 coredump 但 tmp下没有找到任何东西 我怎样才能找到错误 PHP 代码中函数的无限循环导致了此错误
  • 如何从程序内部获取指向程序的特定可执行文件部分的指针? (也许是诽谤)

    我在 Linux 环境中 需要编写一个程序来检索放置在其可执行文件的某个部分中的一些数据 那么 如何从程序内部获取指向程序某个部分 通过其名称 的指针呢 我知道可以使用elf getdata 将节的索引作为参数传递给 get 和Elf Da
  • 如何使用 VSCode 调试 Linux 核心转储?

    我故意从我使用 VSCode 编写的 C 应用程序生成核心转储 我不知道如何调试核心转储 有没有人愿意分享这方面的经验 更新 我相信我现在已经可以使用了 我为核心文件创建了第二个调试配置 我需要添加指向生成的转储文件的 coreDumpPa
  • 如果输入被重定向则执行操作

    我想知道如果我的输入被重定向 我应该如何在 C 程序中执行操作 例如 假设我有已编译的程序 prog 并且我将输入 input txt 重定向到它 我这样做 prog lt input txt 我如何在代码中检测到这一点 一般来说 您无法判
  • Java时区混乱

    我正在运行 Tomcat 应用程序 并且需要显示一些时间值 不幸的是 时间快到了 还有一个小时的休息时间 我调查了一下 发现我的默认时区被设置为 sun util calendar ZoneInfo id GMT 08 00 offset
  • 打印本周星期一的日期(在 bash 中)

    我想获取本周星期一的 YYYYMMdd 格式的日期 例如 今天是 20110627 从明天到周日 我仍然想打印周一 今天 的日期 然后下周重复这个过程 monday date dmonday Y m d last monday date d
  • 当在 python linux 中执行命令 os.system() 时,在 python 中给出响应 yes/no

    考虑一个像这样的命令 yum install boto 当我在终端中执行时 要继续 会询问我是 否 我可以像这样用 python 回应它吗 os system yum install boto Next Yes 将通过相同的 python
  • 如何使用 nohup 获取正在运行的程序列表

    我正在通过 SSH 连接访问运行 CentOS linux 发行版 的服务器 由于我无法始终保持登录状态 因此我使用 nohup command 来运行我的程序 我找不到如何获取我开始使用 nohup 的所有程序的列表 工作 只有在我注销之
  • C++ Linux GCC 应用程序中的 GUID

    我有很多服务器运行这个 Linux 应用程序 我希望他们能够生成一个碰撞概率较低的 GUID 我确信我可以从 dev urandom 中提取 128 个字节 这可能没问题 但是有没有一种简单易用的方法来生成与 Win32 更等效的 GUID
  • 通过名称获取进程ID

    我想在 Linux 下获得一个给定其名称的进程 ID 有没有一种简单的方法可以做到这一点 我还没有在 C 上找到任何可以轻松使用的东西 如果追求 易于使用 char buf 512 FILE cmd pipe popen pidof s p
  • 用于 e NetworkManager VPN 连接的 dbus 信号处理程序

    我需要开发一些在建立 VPN 连接时执行的 python 代码 VPN 由 NetworkManager 控制 我试图弄清楚如何为此使用 NM DBUS 事件 使用 dbus monitor system 我能够识别连接信号 signal
  • 打印 STDOUT/STDERR 并将它们写入 Bash 中的文件?

    有没有办法让 Bash 将 STDOUT STDERR 重定向到文件 但仍然将它们打印到终端 这会将 STDOUT 和 STDERR 重定向到同一个文件 some command 2 gt 1 tee file log Example to
  • 如何在C程序中直接改变显存映射来绘制像素(无需库函数)

    是否可以通过使用 C 程序更改 RAM 中屏幕 视频即监视器 内存映射中的值来显示黑点 我不想使用任何库函数 因为我的主要目标是学习如何开发简单的操作系统 我尝试访问起始屏幕内存映射 即 0xA0000 在 C 中 我尝试运行该程序 但由于
  • 进程如何知道它已收到信号

    如果我错了 请纠正我 以下是我对信号的理解 据我所知 信号生成 和信号传递有2个不同 事物 为了产生信号 操作系统只是在位数组中设置一个位 在过程控制中维护 工艺块 PCB 每一位 对应于特定信号 当设置一个位时 这意味着 该位对应的信号为
  • sudo pip install python-Levenshtein 失败,错误代码 1

    我正在尝试在 Linux 上安装 python Levenshtein 库 但每当我尝试通过以下方式安装它时 sudo pip install python Levenshtein 我收到此错误 命令 usr bin python c 导入
  • tar.gz 和 tgz 是同一个东西吗? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我创建了 tgz 文件tar czvf filecommand then 我最终得到了一个 tgz 文件 我想知道它和tar gz 之间的
  • 如何从 Linux 命令行确定 LCD 显示器是否打开

    如何通过 Linux 命令行判断计算机的显示器是否打开 关闭 我传统上认为显示器是仅输出的设备 但我注意到 Gnome 显示器首选项对话框具有 检测显示器 功能 这可以推广到确定显示器是否物理关闭吗 VESA DDC 连接是I2C http
  • python 可以检测它运行在哪个操作系统下吗?

    python 可以检测操作系统 然后为文件系统构建 if else 语句吗 我需要将 Fn 字符串中的 C CobaltRCX 替换为 FileSys 字符串 import os path csv from time import strf
  • 如何从外部模块导出符号?

    我在内核源代码树之外进行编码 有两个模块 第一个printt有一个功能printtty 将字符串打印到当前 tty 以及第二个模块hello这会调用printtty 在初始化期间 我已经添加了EXPORT SYMBOL printtty 在

随机推荐

  • docker各种报错解决

    目录 问题1 Get https registry 1 docker io v2 context deadline exceeded 解决方法 问题反思 问题2 Error response from daemon Get 34 https
  • org.slf4j.Logger无法输出日志的BUG

    场景 依赖 lt dependency gt lt groupId gt org apache zookeeper lt groupId gt lt artifactId gt zookeeper lt artifactId gt lt v
  • 【安全知识】——SSH的两种远程登录方法详解

    作者名 xff1a Demo不是emo 主页面链接 xff1a 主页传送门 博主简介 xff1a 一 个普通的大二学生 xff0c 在CSDN写博客主要是为了分享自己的学习历程 xff0c 学习方法 xff0c 总结的经验等等 xff0c
  • scanf()函数错误C4996解决办法(严重性 代码 说明 项目 文件 行 禁止显示状态)

    问题如下 xff1a 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4996 scanf This function or variable may be unsafe Consider using scanf s instea
  • Uubuntu 更新内核出现的问题_libssl3

    电脑系统 xff1a ubuntu系统 ubuntu版本 xff1a 1804 内核版本 xff1a 5 17 15 内核从5 15 升级到5 17后 xff0c 1 xff0c 在安装N卡驱动的时候 xff0c error xff1a l
  • 总结Vue中index.html、main.js、App.vue、index.js之间关系以及Vue项目加载流程

    总结Vue中index html main js App vue index js之间关系以及Vue项目加载流程 文章目录 总结Vue中index html main js App vue index js之间关系以及Vue项目加载流程1
  • 安装Hisat2

    一 xff08 MobaXterm Personal xff09 安装aspera 首先进行预编译解压安装 xff1a mkdir Biosofts unzip hisat2 2 2 1 Linux x86 64 zip d Biosoft
  • 【linux】基于阻塞队列的生产者消费者模型(条件变量)

    文章目录 一 引入二 生产者消费者模型2 1 三者关系2 2 生产者消费者模型基本原则2 3 生产者消费者模型的好处 三 基于阻塞队列的生产者消费者模型3 1 原理3 2 代码实现3 3 pthread cond wait的第二个参数3 4
  • 重建linux系统的grub启动项

    鉴于没钱买新电脑 xff0c 所以在一个电脑上装了3个系统 xff0c Ubuntu xff0c ArchLinux和Windows 然而Windows系统重启经常会导致找不到Arch的启动项 xff0c 即在UEFI里找不到Arch的gr
  • python程序开机自启(打包成exe文件开机自启)

    import shutil import sys import os import getpass a 61 os path basename sys argv 0 获取自身文件名 d 61 getpass getuser 获取用户名 b
  • 【Linux】题解:生产者与消费者模型(附源代码)

    Linux 题解 xff1a 生产者与消费者模型 xff08 附源代码 xff09 摘要 xff1a 本文主要介绍生产者与消费者模型 xff0c 其中主要内容分为对该模型的介绍及分析 xff0c 阻塞队列实现该模型 xff0c 并对其升级实
  • Spring之bean对象

    目录 一 了解Bean 二 Bean的生命周期 三 Bean的应用 单例模式 多例模式 单例模式与多例模式优劣势 xff1a 一 了解javaBean 什么是javaBean对象 span style background color fb
  • SpringMVC入门

    目录 一 Springmvc简介及配置 导入pom依赖 二 Springmvc之helloworld实现 配置tomcat服务器 根据图片进行操作 三 Springmvc常用注解及返回处理 四 增删改查 一 Springmvc简介及配置 1
  • Spring Boot 之主启动类

    主启动类 文章目录 主启动类 64 SpringBootApplication作用 64 SpringBootConfiguration 64 EnableAutoConfiguration 64 AutoConfigurationPack
  • 计算机组成原理期末考试试题及答案

    计算机组成原理期末考试试题及答案 一 选择题 1 完整的计算机系统应包括 D A 运算器 存储器和控制器 B 外部设备和主机 C 主机和实用程序 D 配套的硬件设备和软件系统 2 计算机系统中的存储器系统是指 D A RAM存储器 B RO
  • python DBSCAN聚类算法

    文章目录 DBSCAN聚类算法基本思想基本概念工作流程参数选择DBSCAN的优劣势 代码分析 61 61 Matplotlib Pyplot 61 61 61 61 make blobs 61 61 61 61 StandardScaler
  • 1 操作系统第一章 操作系统概念、功能、四大特征、操作系统发展与分类

    文章目录 1 1 操作系统概念1 2 操作系统功能1 3 操作系统四大特征1 3 1 并发1 3 2 共享1 3 3 并发性和共享区别及对应关系 xff1a 1 3 4 虚拟1 3 5 异步 1 4 操作系统的发展与分类1 4 1 手工操作
  • 删除数组中指定的数字

    删除数组中指定的数字 题目 xff1a 有一个整数序列 xff08 可能有重复的整数 xff09 xff0c 现删除指定的某一个整数 xff0c 输出删除指定数字之后的序列 xff0c 序列中未被删除数字的前后位置没有发生改变 代码实现如下
  • session与Cookie

    目录 session与Cookie 1 session的一些方法与概述 2 Cookie的一些方法与概述 Cookie保存的类容如含特殊符号 需要转16径直文件 session与Cookie 1 session的一些方法与概述 sessio
  • 【Linux信号量】

    Linux信号量 POSIX信号量信号量的原理信号量的概念信号量函数 二元信号量模拟实现互斥功能基于环形队列的生产消费模型空间资源和数据资源生产者和消费者申请和释放资源必须遵守的两个规则信号量保护环形队列的原理 POSIX信号量 信号量的原