前言
一、匿名管道
#include <unistd.h>
功能:創(chuàng)建一無名管道
原型
int pipe(int fd[2]);
參數(shù):
fd:文件描述符數(shù)組,其中fd[0]表示讀端, fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤代碼
1.管道原理
本質是先讓不同的進程看到同一份資源,也就是兩個進程都能對管道文件的緩沖區(qū)進行操作
這里我們pipe的時候,會使用兩個文件描述符,這兩個文件描述里面存的file結構體是同一個,也就是管道文件的file結構體,file結構體中存儲有inode以及系統(tǒng)緩沖區(qū),此時fork一個子進程,子進程有著和父進程一樣的結構,
這里有一個非常重要的點雖然子進程有著自己的進程地址空間,也有著自己存儲file結構體的指針數(shù)組,但是其數(shù)組里面的內容是和父進程一樣的,也就是子進程里面pipe對應的文件描述符位置指向的file結構體(管道文件)是同一個,至此我們父子進程就看到了同一個資源,可以利用這個資源進行通信
兩個不同的進程打開同一份文件的時候,在內核中,操作系統(tǒng)只會打開一個
2.管道的四種情況
1.讀寫端正常,管道如果為空,讀端就要阻塞
讀寫端正常,管道如果被寫滿,寫端就要阻塞
2.讀端正常讀,寫端關閉,讀端就會讀到0,表明讀到了管道文件的結尾,不會被阻塞,如果我們打印讀端讀到的內容,顯示器會一直顯示0
3.寫端正常寫入,讀端關閉,操作系統(tǒng)會殺掉此時正在寫入的進程(通過信號來殺掉)
4.因為操作系統(tǒng)不會做低效,浪費的事情,我讀端都不讀了,你寫入再多數(shù)據(jù)到一個管道里面有什么用,因為管道不占用磁盤內存,所以程序結束后,就沒有管道的存在了。
當要寫入的數(shù)據(jù)量不大于PIPE_BUF時,linux將保證寫入的原子性。
當要寫入的數(shù)據(jù)量大于PIPE_BUF時,linux將不再保證寫入的原子性。
3.管道的特點
1.只能用于具有共同祖先的進程(具有親緣關系的進程)之間進行通信;通常,一個管道由一個進程創(chuàng)建,然后該進程調用fork,此后父、子進程之間就可應用該管道。
2.管道提供流式服務
3.一般而言,進程退出,管道釋放,所以管道的生命周期隨進程
4.一般而言,內核會對管道操作進行同步與互斥
5.管道是半雙工的,數(shù)據(jù)只能向一個方向流動;需要雙方通信時,需要建立起兩個管道
二、命名管道
1. 特點
1.管道應用的一個限制就是只能在具有共同祖先(具有親緣關系)的進程間通信。
2.如果我們想在不相關的進程之間交換數(shù)據(jù),可以使用FIFO文件來做這項工作,它經(jīng)常被稱為命名管道
3.命名管道是一種特殊類型的文件
2.創(chuàng)建命名管道
1.在命令行上
mkfifo +文件名
2.在程序中
int mkfifo(const char *filename,mode_t mode);
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
3.一個程序執(zhí)行打開管道并不會真正打卡
我們執(zhí)行這個程序發(fā)現(xiàn)并沒有打印那句話,說明管道文件并沒有真正打開,只有當我們執(zhí)行另一個我們要通信的文件的時候,管道才會真正打開
三、進程池簡易實現(xiàn)
1.makefile
ProcessPool:ProcessPool.cpp
g++ -o $@ $^ -std=c++11 -g
.PHONY:clean
clean:
rm -rf ProcessPool
2.Task.hpp
#pragma once
#include<functional>
#include<vector>
#include<iostream>
using namespace std;
void task1()
{
std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{
std::cout << "lol 更新野區(qū),刷新出來野怪" << std::endl;
}
void task3()
{
std::cout << "lol 檢測軟件是否更新,如果需要,就提示用戶" << std::endl;
}
void task4()
{
std::cout << "lol 用戶釋放技能,更新用的血量和藍量" << std::endl;
}
void LoadTask(vector<function<void()>>*tasks){
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task3);
tasks->push_back(task4);
return ;
}
3.ProcessPool.cpp
我們創(chuàng)建processnum個子進程,讓父進程來寫,子進程來讀,子進程讀到任務號后進行對應的處理。
#include <iostream>
#include "Task.hpp"
#include <assert.h>
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
vector<function<void()>> tasks;
const int processnum = 10;//創(chuàng)建的子進程數(shù)
class channel
{
public:
channel( string processname, pid_t slaverid,int cmdcode)
: _processname(processname), _cmdfd(cmdcode), _slaverid(slaverid)
{
}
public:
string _processname;//執(zhí)行任務的進程名
pid_t _slaverid;//執(zhí)行任務的進程pid
int _cmdfd;//朝幾號管道去操作
};
void Menu()
{
std::cout << "################################################" << std::endl;
std::cout << "# 1. 刷新日志 2. 刷新出來野怪 #" << std::endl;
std::cout << "# 3. 檢測軟件是否更新 4. 更新用的血量和藍量 #" << std::endl;
std::cout << "# 0. 退出 #" << std::endl;
std::cout << "#################################################" << std::endl;
}
void slaver()
{
int cmdcode;
while (true)
{
int n = read(0, &cmdcode, sizeof(int));//讀取任務碼
if (n == sizeof(int))
{
cout << "slaver say get a command " << getpid() << " cmdcode: " << cmdcode << endl;
if (cmdcode >= 0 && cmdcode < tasks.size())
tasks[cmdcode]();//執(zhí)行任務
}
else if (n == 0)//為0,說明讀到文件末尾,之間break
break;
}
}
void InitProcessPool(vector<channel> *channels)
{
for (int i = 0; i < processnum; i++)
{
int pipefd[2] = {0};
int n = pipe(pipefd);
//使用兩個文件描述符指向同一個管道文件
assert(!n);
pid_t id = fork();
if (id == 0)//子進程
{
close(pipefd[1]);//關閉寫文件
dup2(pipefd[0], 0);//將讀文件重定向到標準輸入的位置
close(pipefd[0]);//關閉當前讀文件,因為我們后續(xù)用標準輸入的下標就行了
slaver();//子進程讀取任務碼
exit(0);
}
string name = "processname " + to_string(i);//子進程名字
channels->push_back(channel(name, id, pipefd[1]));//子進程pid,這個子進程
//與父進程之間的管道文件描述符下標記錄下來
// father
close(pipefd[0]);//關閉讀文件
}
}
void ctrlProcess(vector<channel> &channels)
{
int which = 0;
//我們循環(huán)調用各個子進程,which為子進程的下標
while (true)
{
Menu();
int select = 0;
cin >> select;
cout << "Please Enter@ ";
if (select <= 0 || select >= 5)
break;
int cmdcode = select - 1;
cout << "father say task have sent to " << channels[which]._processname << " cmdcode : " << cmdcode << endl;
write(channels[which]._cmdfd, &cmdcode, sizeof(int));//寫入指令
which++;
which %= channels.size();
}
}
void QuitProcess(const vector<channel> channels)
{
//方法一:
for (const auto &c : channels)
close(c._cmdfd);
for (const auto &c : channels)
waitpid(c._slaverid, nullptr, 0);
//方法二:
//for(int i=channels.size()-1;i>=0;i--){
// close(channels[i]._cmdfd);
//waitpid(channels[i]._slaverid,nullptr,0);//阻塞等待
//}
}
int main()
{
vector<channel> channels;//管理管道的數(shù)組
LoadTask(&tasks);//加載任務
InitProcessPool(&channels);//初始化進程池
ctrlProcess(channels);//輸入任務命令
QuitProcess(channels);//中止進程
return 0;
}
如果等待和close在一個循環(huán)中會發(fā)生阻塞,因為我一號管道雖然父進程那里寫關閉了,但依舊有子進程2,3指向這個管道為寫文章來源:http://www.zghlxwxcb.cn/news/detail-754336.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-754336.html
到了這里,關于【Linux】匿名管道與命名管道,進程池的簡易實現(xiàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!