【Linux】共享内存|循环队列|信号量 学习笔记
日期:2025.1.29
学习内容:
- 共享内存
- 循环队列
- 信号量
个人总结:
共享内存:
这里的内容稍微有一点点的抽象,不过还好。先上整体的代码比较好讲。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
struct node {
int id;
char name[51];
};
int main(int argc, char* argv[]) {
int shmid = shmget(0x5005, sizeof(node), IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(-1);
}
cout << "shmid:" << shmid << endl;
node* ptr = (node*)shmat(shmid, 0, 0);
if (ptr == (void*)-1) {
perror("shmat");
exit(-1);
}
cout << "Pre:" << ptr->id << " " << ptr->name << endl;
ptr->id = atoi(argv[1]);
strcpy(ptr->name, argv[2]);
cout << "Now:" << ptr->id << " " << ptr->name << endl;
shmdt(ptr);
// if (shmctl(shmid, IPC_RMID, 0) == -1) {
// perror("shmctl");
// }
return 0;
}
- shmget函数:参数依次代表:共享内存唯一标识符(可以理解成一段地址,用来找到共享内存段),共享内存段的大小,权限。返回值是共享内存段标识符
- shmat:参数依次代表:共享内存段标识符,0,0,后面两个0是默认的,这里不过多考虑。返回值是一个地址,我们可以用强制转换指针。当失败时是(void*) -1;
- shmdt:参数:指针。用来断开当前进程与共享内存段的连接
- shmctl:参数:共享内存标识符,IPC_RMID(用于删除共享内存段),0(删除时用,或者用NULL也可以)。
这一段代码运行两次,会发现他的答案是有记录下来的而不是每一次都重置,就是因为内存共享,可以直接读取到了上一次存储在地址的数据。
小tip是:
我们不能用那些内存地址开在栈上的容器,例如vector,string,还有一些局部变量,因为局部变量的地址每次都是独特的,而数组(全局)的内存是静态分配的,即它的大小和位置在编译时就已经确定。
循环队列:
循环队列的定义就不讲了,其实就是一个队列,但是head和tail是循环的,在固定空间的情况下。
然后基于用这个再写一遍共享内存便是本次的work。
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <fstream>
#include <ostream>
#include <sys/sem.h>
#include <sys/types.h>
#include <cstring>
#include <mutex>
#include <atomic>
using std::cout;
using std::endl;
template<class T,int MaxLength>
class fqueue {
T data[MaxLength];
int f_head;
int f_tail;
int f_size;
int f_max_size;
std::once_flag f_init_flag;
void initialize() {
f_head = 0;
f_tail = MaxLength-1;
f_size = 0;
f_max_size = MaxLength;
memset(data, 0, MaxLength * sizeof(T));
}
public:
fqueue() { init(); }
void init() {
std::call_once(f_init_flag, [this]() {this->initialize(); });
}
bool push(T one_data) {
if (f_size >= f_max_size) {
return 0;
}
f_tail = (f_tail + 1) % f_max_size;
data[f_tail] = one_data;
f_size++;
return 1;
}
int size() {return f_size;}
int maxSize() {return f_max_size;}
bool empty() {if (f_size == 0) return 1; else return 0;}
T& front() {return data[f_head];}
bool pop() {
if (f_size <= 0) return 0;
f_head = (f_head + 1) % f_max_size;
f_size--;
return 1;
}
void print() {
for (int i = f_head; i != f_tail; i = (i + 1) % f_max_size) {
cout << data[i] << " ";
}
cout << data[f_tail] << endl;
}
};
问题:关于多线程共享内存的初始化
在写循环队列的时候,由于当我们创建一个fqueue的指针的时候,无法使用构造函数,所以我们需要手动初始化。
这里的fqueue的类,我们设置的是希望只会调用最多一次init函数。所以这个地方的处理就是一个问题。
我们不能直接bool f_inited。用一个变量来代表有没有初始化过,毕竟变量的初始值是未知的,使用 if (f_inited) return;这种语句是比较危险的。
这里可以使用一个C++11的新特性:std::once_flag,这个变量与std::call_once函数搭配,这个函数一共两个参数,第一个是std::once_flag的变量,第二个是要做的事情,可以写lamba表达式。
就如同贴上去的代码,这样子就避免了多线程共享内存时会出现多次初始化或者无法初始化的情况。
问题:由于shmget函数返回-1
在使用shmget函数的时候发现竟然返回了-1,然后发现是因为共享内存段的键值,换了个键值就好了。。。但是我还是不知道因为什么出的错。。。。。找了半天没啥用额。。。。
但是关于删除键值的操作是ipcs -m ,然后ipcrm -m 加上共享内存id就好了。
信号量
class csemp {
union semun {
int val;
struct semid_ds* buf;
unsigned short* array;
};
int m_semid;
short m_sem_flg;
public:
csemp() :m_semid(-1) {}
bool init(key_t key, unsigned short val = 1, short sem_flg = SEM_UNDO);
bool wait(short sem_op = -1);
bool post(short sem_op = 1);
int getValue();
bool destory();
// ~csemp();
};
bool csemp::init(key_t key, unsigned short val , short sem_flg ) {
if (m_semid != -1) return false;
m_sem_flg = sem_flg;
if ((m_semid = semget(key, 1, 0666)) == -1) {
if (errno == ENOENT) {
if ((m_semid = semget(key, 1, IPC_CREAT | 0666 | IPC_EXCL)) == -1) {
if (errno == EEXIST) {
if ((m_semid = semget(key, 1, 0666)) == -1) {
perror("init 1 semget()");
return false;
}
return true;
}
else {
perror("init 2 semget()");
return false;
}
}
union semun sem_union;
sem_union.val = val;
if (semctl(m_semid, 0, SETVAL, sem_union) < 0) {
perror("init semctl()");
return false;
}
}
else {
return false;
}
}
return true;
}
bool csemp::wait(short val) {
if (m_semid == -1) return false;
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = val;
sem_b.sem_flg = m_sem_flg;
if (semop(m_semid, &sem_b, 1) == -1) {
perror("p semop()");
return false;
}
return true;
}
bool csemp::post(short val) {
if (m_semid == -1) return false;
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = val;
sem_b.sem_flg = m_sem_flg;
if (semop(m_semid, &sem_b, 1) == -1) {
perror("V semop()");
return false;
}
return true;
}
int csemp::getValue() {
return semctl(m_semid, 0, GETVAL);
}
bool csemp::destory() {
if (m_semid == -1) return false;
if (semctl(m_semid, 0, IPC_RMID) == -1) {
perror("destory semctl()");
return false;
}
return true;
}
有关这里实现的细节自己现在不怎么能看懂,这里埋一个坑,以后来填。
用这个代码去实现共享内存里的代码,实现一个上锁解锁的操作,就跟互斥锁一样。
贴上代码:
csemp mx;
if (mx.init(0x5005) == false) {
return -1;
}
cout << "申请加锁..." << endl;
mx.wait();
cout << "加锁成功..." << endl;
cout << "Pre:" << ptr->id << " " << ptr->name << endl;
ptr->id = atoi(argv[1]);
strcpy(ptr->name, argv[2]);
cout << "Now:" << ptr->id << " " << ptr->name << endl;
sleep(10);
mx.post();
cout << "解锁" << endl;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验