绵阳专业网站建设,网站做xss过滤,李沧网站建设公司,如何创建企业邮箱文章目录 进程间通信介绍进程间通信目的进程间通信发展 管道什么是管道 匿名管道用fork来共享管道原理站在文件描述符角度-深度理解管道站在内核角度-管道本质管道读写规则管道特点 命名管道创建一个命名管道匿名管道与命名管道的区别命名管道的打开规则 命名管道的删除用命名管… 文章目录 进程间通信介绍进程间通信目的进程间通信发展 管道什么是管道 匿名管道用fork来共享管道原理站在文件描述符角度-深度理解管道站在内核角度-管道本质管道读写规则管道特点 命名管道创建一个命名管道匿名管道与命名管道的区别命名管道的打开规则 命名管道的删除用命名管道实现文件拷贝用命名管道实现serverclient通信 system V共享内存共享内存示意图共享内存数据结构共享内存函数 system V信号量 选学了解即可 进程间通信介绍
进程间通信目的
数据传输一个进程需要将它的数据发送给另一个进程资源共享多个进程之间共享同样的资源。通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。
进程间通信发展
进程间通信IPC的发展经历了多个阶段主要包括 管道Pipes最早期的IPC机制之一允许具有亲缘关系的进程如父子进程进行通信。管道是半双工(某一时刻只允许信号在管道上的单向通信)的数据只能单向流动。 命名管道Named Pipes与管道类似但它们允许没有亲缘关系的进程通信并且可以在文件系统中有一个名称。 System V IPC这是一组传统的IPC机制包括消息队列、信号量和共享内存。这些机制允许不同进程共享和传递复杂的数据结构。 POSIX IPC基于System V IPC提供了更加标准化和可移植的IPC机制包括消息传递、同步以及共享内存的新方法。 套接字Sockets用于不同机器上的进程通信也可以在同一台机器上的进程之间通信非常灵活。 远程过程调用RPC允许一个进程调用另一个进程的函数或方法就像调用本地函数一样。 内存映射文件Memory-mapped files通过映射文件到内存不同进程可以访问同一块内存区域实现通信。 信号Signals用于进程间的简单通信一个进程可以向另一个进程发送信号以通知某些事件的发生。
随着技术的发展IPC机制也在不断进步以满足更高效、更安全的进程通信需求。全部的进程间通信虽然体系庞大但是其基本思想都离不开操作系统的性质–先描述再组织。我们只需学习早期的通信方法管道和System V通信触类旁通。
管道
什么是管道
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。 匿名管道
匿名管道Anonymous Pipe是一种在进程间进行单向通信的机制其中一个进程作为写入端另一个进程作为读取端。它是一种简单而有效的进程间通信方式常用于父子进程或兄弟进程之间的通信。
在Linux系统中可以使用pipe函数来创建匿名管道。pipe函数会创建一个管道并返回两个文件描述符一个用于读取数据另一个用于写入数据。其中文件描述符0表示标准输入stdin文件描述符1表示标准输stdout。
#include unistd.h
int pipe(int fd[2]);
//创建失败返回-1创建成功返回两个文件描述符构成的数组以下是使用匿名管道进行进程间通信的基本步骤
调用pipe函数创建管道获取读取端和写入端的文件描述符。调用fork函数创建一个子进程。在父进程中关闭读取端的文件描述符保留写入端的文件描述符。在子进程中关闭写入端的文件描述符保留读取端的文件描述符。父进程可以通过写入端的文件描述符向管道写入数据。子进程可以通过读取端的文件描述符从管道读取数据。
下面是一个简单的示例代码演示了如何使用匿名管道进行进程间通信
#include stdio.h
#include unistd.hint main() {int pipefd[2];char buffer[20];pid_t pid;// 创建管道if (pipe(pipefd) -1) {perror(pipe);return 1;}// 创建子进程pid fork();if (pid -1) {perror(fork);return 1;}if (pid 0) {// 子进程close(pipefd[1]); // 关闭写入端// 从管道读取数据read(pipefd[0], buffer, sizeof(buffer));printf(子进程接收到的数据%s\n, buffer);close(pipefd[0]); // 关闭读取端} else {// 父进程close(pipefd[0]); // 关闭读取端// 向管道写入数据write(pipefd[1], Hello, child process!, 21);close(pipefd[1]); // 关闭写入端}return 0;
}在上述示例中父进程向管道写入了一段字符串子进程从管道中读取该字符串并输出。通过匿名管道父子进程之间实现了简单的通信。 需要注意的是匿名管道只能实现单向通信如果需要双向通信可以考虑使用命名管道Named Pipe或其他进程间通信机制如共享内存或消息队列。
用fork来共享管道原理 站在文件描述符角度-深度理解管道 站在内核角度-管道本质 所以看待管道就如同看待文件一样管道的使用和文件一致迎合了“Linux一切皆文件思想”。
管道读写规则
当没有数据可读时
O_NONBLOCK disableread调用阻塞即进程暂停执行一直等到有数据来到为止。O_NONBLOCK enableread调用返回-1errno值为EAGAIN。 当管道满的时候O_NONBLOCK disable write调用阻塞直到有进程读走数据O_NONBLOCK enable调用返回-1errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭则read返回0如果所有管道读端对应的文件描述符被关闭则write操作会产生信号SIGPIPE,进而可能导致write进程
退出当要写入的数据量不大于PIPE_BUF时linux将保证写入的原子性。当要写入的数据量大于PIPE_BUF时linux将不再保证写入的原子性。管道特点
只能用于具有共同祖先的进程具有亲缘关系的进程之间进行通信通常一个管道由一个进程创建然后该进程调用fork此后父、子进程之间就可应用该管道。管道提供流式服务。一般而言进程退出管道释放所以管道的生命周期随进程。一般而言内核会对管道操作进行同步与互斥。管道是半双工的数据只能向一个方向流动需要双方通信时需要建立起两个管道。如下图
命名管道
管道应用的一个限制就是只能在具有共同祖先具有亲缘关系的进程间通信。如果我们想在不相关的进程之间交换数据可以使用FIFO文件来做这项工作它经常被称为命名管道。命名管道是一种特殊类型的文件。
创建一个命名管道
命名管道可以从命令行上创建命令行方法是使用下面这个命令
mkfifo filename其中filename 是要创建的命名管道的路径和名称。 例如要在命令行上创建一个名为 “myfifo” 的命名管道可以执行以下命令
mkfifo myfifo执行该命令后系统将在当前目录下创建一个名为 “myfifo” 的命名管道文件。
需要注意的是命名管道是一种特殊的文件可以像普通文件一样进行读写操作。在命令行中创建的命名管道可以被其他进程打开并进行读写操作。
命名管道也可以从程序里创建相关函数有 mkfifo 是一个用于创建命名管道Named Pipe的系统调用。它可以在文件系统中创建一个特殊的文件用于进程间的通信。
具体使用方法如下
#include sys/types.h
#include sys/stat.hint mkfifo(const char *pathname, mode_t mode);pathname要创建的命名管道的路径和名称。mode创建的命名管道的权限模式。
返回值
成功返回0。失败返回-1并设置相应的错误码。
使用示例
#include stdio.h
#include sys/types.h
#include sys/stat.hint main() {const char *pathname myfifo;mode_t mode 0666; // 设置权限为读写所有者、组和其他用户if (mkfifo(pathname, mode) -1) {perror(mkfifo);return 1;}printf(命名管道创建成功%s\n, pathname);return 0;
}在上述示例中我们调用了 mkfifo 函数创建了一个命名管道并指定了路径和名称为 “myfifo”权限模式为 0666表示读写所有者、组和其他用户都有权限。
需要注意的是命名管道是一种特殊的文件可以像普通文件一样进行读写操作。在进程间通信时一个进程可以将数据写入命名管道而另一个进程可以从该管道读取数据。但是需要注意管道的读取和写入操作应该在不同的进程中进行否则可能会造成阻塞。
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建打开用openFIFO命名管道与pipe匿名管道之间唯一的区别在它们创建与打开的方式不同一但这些工作完成之后它们具有相同的语义。
命名管道的打开规则
对于命名管道FIFO的打开规则取决于打开操作是为读还是为写。 当打开操作为读时 如果打开操作没有设置 O_NONBLOCK 标志即阻塞模式则打开操作会阻塞直到有其他进程以写模式打开该命名管道。如果打开操作设置了 O_NONBLOCK 标志即非阻塞模式则打开操作会立即返回成功不会阻塞等待。 当打开操作为写时 如果打开操作没有设置 O_NONBLOCK 标志即阻塞模式则打开操作会阻塞直到有其他进程以读模式打开该命名管道。如果打开操作设置了 O_NONBLOCK 标志即非阻塞模式则打开操作会立即返回失败并返回错误码 ENXIO表示设备或地址不存在。
这些规则确保了在读写操作之间建立正确的通信连接。如果读操作和写操作同时进行并且满足打开规则那么数据可以在进程之间通过命名管道进行传输。
需要注意的是打开规则只适用于命名管道的打开操作而不是创建操作。创建命名管道时并不需要等待其他进程的打开操作。只有在打开操作时才会根据规则进行阻塞或立即返回。
命名管道的删除
用命名管道实现文件拷贝
#includestdio.h
#includeunistd.h
#includesys/types.h
#includesys/stat.h
#includestring.h
#includefcntl.h
#includesys/wait.h
#includestdlib.h
#define BUFFER_SIZE 50
int main()
{const char *pathname myfifo;mode_t mode 0666; // 设置权限为读写所有者、组和其他用户if (mkfifo(pathname, mode) -1){perror(mkfifo);return 1;}printf(命名管道创建成功%s\n, pathname);// 打开源文件和目标文件 int source_fd open(./text.txt, O_RDONLY);if (source_fd -1) {perror(open source file);return 1;}int destination_fd open(./popytext.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (destination_fd -1) {perror(open destination file);return 1;}// 打开命名管道int fifo_fd open(myfifo, O_RDWR);if (fifo_fd -1) {perror(open fifo);return 1;}// 创建子进程进行数据传输pid_t pid fork();if (pid -1) {perror(fork);return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read;if (pid 0) {// 子进程从命名管道中读取数据并写入目标文件if((bytes_read read(fifo_fd, buffer, BUFFER_SIZE)) 0) {write(destination_fd, buffer, bytes_read);}exit(EXIT_SUCCESS);}// 父进程从源文件中读取数据并写入命名管道if((bytes_read read(source_fd, buffer, BUFFER_SIZE)) 0) {write(fifo_fd, buffer, bytes_read);}close(source_fd);close(fifo_fd);close(destination_fd);// 等待子进程结束wait(NULL);// 删除命名管道unlink(myfifo);return 0;
}用命名管道实现serverclient通信
comm.hpp
#pragma once
#includeiostream
#includecerrno
#includecstring
#includecstdlib
#includesys/types.h
#includesys/stat.h
#includeunistd.h
#includefcntl.h#define FIFO_FILE ./myfifo
#define MODE 0664
enum{FIFO_CREATE_ERR 1,FIFO_DELETE_ERR, FILE_OPEN_ERR
};
class Init
{
public:Init(){int n mkfifo(FIFO_FILE,MODE);if(n -1){//printf(%d:%s\n,errno,strerror(errno));perror(mkfifo);exit(FIFO_CREATE_ERR);}}~Init(){int m unlink(FIFO_FILE);if(m -1){perror(unlink);exit(FIFO_DELETE_ERR);}}
};log.hpp
#pragma once#include iostream
#include time.h
#include stdarg.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdlib.h#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile log.txtclass Log
{
public:Log(){printMethod Screen;path ./log/;}void Enable(int method){printMethod method;}std::string levelToString(int level){switch (level){case Info:return Info;case Debug:return Debug;case Warning:return Warning;case Error:return Error;case Fatal:return Fatal;default:return None;}}void printLog(int level, const std::string logtxt){switch (printMethod){case Screen:std::cout logtxt std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string logname, const std::string logtxt){std::string _logname path logname;int fd open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // log.txtif (fd 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string logtxt){std::string filename LogFile;filename .;filename levelToString(level); // log.txt.Debug/Warning/FatalprintOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t time(nullptr);struct tm *ctime localtime(t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), [%s][%d-%d-%d %d:%d:%d], levelToString(level).c_str(),ctime-tm_year 1900, ctime-tm_mon 1, ctime-tm_mday,ctime-tm_hour, ctime-tm_min, ctime-tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式默认部分自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), %s %s\n, leftbuffer, rightbuffer);// printf(%s, logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};
server.cc
#includecomm.hpp
#includelog.hppusing namespace std;//管理管道文件
int main()
{/*logmessage(Info,hello linux);*///创建信道Init init;//打开管道int fd open(FIFO_FILE,O_RDONLY);if(fd0){//perror(open);logmessage(Fatal,error string:%s,error code:%d,strerror(errno),errno);exit(FILE_OPEN_ERR);}logmessage(Info,error string:%s,error code:%d,strerror(errno),errno);//开始通信while(true){char buffer[1024]{0};int x read(fd,buffer,sizeof(buffer));if(x0){buffer[x]0;coutclient say#bufferendl;}else if(x0){logmessage(Debug,client quit,me too!:%s,error code:%d,strerror(errno),errno);break;}elsebreak;}//关闭管道 close(fd);return 0;
}client.cc
#includecomm.hpp
using namespace std;int main()
{int fd open(FIFO_FILE,O_WRONLY);if(fd0){perror(open);exit(FILE_OPEN_ERR);}string line;while(true){coutPlease Enter;getline(cin,line);write(fd,line.c_str(),line.size());}close(fd);return 0;
}system V共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间这些进程间数据传递不再涉及到内核换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存示意图 共享内存数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};这段代码定义了 struct shmid_ds 结构体用于描述 System V 共享内存段的属性和状态。让我们逐个解释这些字段的含义 shm_perm一个 struct ipc_perm 类型的结构体用于描述共享内存段的权限信息包括所有者、所属组和访问权限等。 shm_segsz表示共享内存段的大小以字节为单位。 shm_atime表示最后一次连接attach共享内存段的时间以 UNIX 时间戳__kernel_time_t 类型表示。 shm_dtime表示最后一次分离detach共享内存段的时间以 UNIX 时间戳表示。 shm_ctime表示最后一次修改共享内存段属性的时间以 UNIX 时间戳表示。 shm_cpid表示创建共享内存段的进程的进程 ID__kernel_ipc_pid_t 类型。 shm_lpid表示最后一次操作共享内存段的进程的进程 ID。 shm_nattch表示当前连接attach到共享内存段的进程数量。 shm_unused一个未使用的字段用于兼容性。 shm_unused2 和 shm_unused3两个未使用的字段可能由某些特定的实现或库使用。
通过使用 struct shmid_ds 结构体可以获取共享内存段的属性信息例如大小、创建时间、连接数等。这些信息对于管理和监控共享内存的使用非常有用。
请注意struct shmid_ds 结构体中的字段类型可能会因操作系统和编译器而异例如 __kernel_time_t 和 __kernel_ipc_pid_t。在实际使用时你需要查阅相关的系统文档或头文件来了解具体的字段类型和定义。
共享内存函数
shmget函数
功能用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
- key用于标识共享内存段的键值。可以使用 ftok 函数生成一个唯一的键值
- 也可以使用已知的键值来获取现有的共享内存段。不同的共享内存段应该使用不同的键值。
- size指定共享内存段的大小以字节为单位。共享内存段的大小应根据实际需求进行设置。
- shmflg用于指定创建共享内存段的标志和访问权限。可以通过按位或运算符 | 组合多
- 个标志。常用的标志包括
- IPC_CREAT如果共享内存段不存在则创建一个新的共享内存段。
- IPC_EXCL不单独使用与 IPC_CREAT 一起使用如果共享内存段已存在则返回错误。
- 0666指定共享内存段的访问权限。这里的权限是八进制表示表示所有者、
- 所属组和其他用户的读写权限。
返回值成功返回一个非负整数即该共享内存段的标识码失败返回-1上面的Key有讲究的:
key是一个数字这个数字是几并不重要。关键在于它必须在内核中具有唯一性能够让不同的进程进行唯一性标识。第一个进程可以通过key创建共享内存第二个之后的进程只要拿着同一个key就可以和第一个进程看到同一个共享内存了!对于一个已经创建好的共享内存key在哪? key在共享内存的描述对象中!第一次创建的时候必须有一个key了。怎么有?可以使用 ftok 函数生成一个唯一的键值也可以使用已知的键值来获取现有的共享内存段。不同的共享内存段应该使用不同的键值。 ftok 函数用于将一个路径名和一个整数标识符转换为一个唯一的键值用于标识 System V 共享内存段、消息队列或信号量集。下面是 ftok 函数的使用方法
#include sys/types.h
#include sys/ipc.hkey_t ftok(const char *pathname, int proj_id);pathname一个指向路径名的字符串可以是任意有效的文件路径。通常选择一个与共享内存相关的文件作为路径名确保不同的共享内存段使用不同的路径名。 proj_id一个整数标识符用于区分不同的共享内存段。通常选择一个非零的整数作为标识符。
ftok 函数将 pathname 和 proj_id 组合起来生成一个唯一的键值。该键值可以用于创建共享内存段、获取现有的共享内存段或操作其他 System V IPC 对象。
需要注意的是ftok 函数的结果并不是绝对唯一的因为它使用了路径名和整数标识符的组合。因此在使用 ftok 生成键值时应确保路径名和标识符的组合在整个系统中是唯一的以避免冲突。
以下是一个示例展示了如何使用 ftok 函数生成一个键值
#include sys/types.h
#include sys/ipc.h
#include stdio.hint main() {const char *pathname /path/to/file; // 路径名int proj_id 1; // 标识符key_t key ftok(pathname, proj_id);if (key -1) {perror(ftok);return 1;}printf(Generated key: %d\n, key);return 0;
}在上述示例中我们使用 /path/to/file 作为路径名1 作为标识符然后调用 ftok 函数生成一个键值。如果成功将打印生成的键值。如果发生错误将打印相应的错误信息。
请注意生成的键值应与其他进程共享以便它们可以使用相同的键值访问相同的共享内存段。
shmat函数
功能将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值成功返回一个指针指向共享内存第一个节失败返回-1说明
shmaddr为NULL核心自动选择一个地址shmaddr不为NULL且shmflg无SHM_RND标记则以shmaddr为连接地址。shmaddr不为NULL且shmflg设置了SHM_RND标记则连接的地址会自动向下调整为SHMLBA的整数倍。公式shmaddr -(shmaddr % SHMLBA)shmflgSHM_RDONLY表示连接操作用来只读共享内存 shmdt函数
功能将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值成功返回0失败返回-1
注意将共享内存段与当前进程脱离不等于删除共享内存段shmctl函数
功能用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作有三个可取值
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值成功返回0失败返回-1System V 共享内存的使用涉及以下几个步骤 创建共享内存段使用 shmget 函数创建一个共享内存段。该函数接受三个参数键值用于标识共享内存段大小指定共享内存的大小和标志用于指定权限和创建方式。成功创建共享内存段后shmget 函数返回一个唯一的标识符共享内存标识符。 连接共享内存使用 shmat 函数将共享内存段连接到进程的地址空间。shmat 函数接受两个参数共享内存标识符和地址如果地址为 NULL则系统会自动选择一个可用的地址。成功连接共享内存后shmat 函数返回共享内存的起始地址。 使用共享内存一旦共享内存连接到进程的地址空间进程可以像使用普通内存一样使用它。可以通过指针访问共享内存中的数据。 分离共享内存使用 shmdt 函数将共享内存从进程的地址空间中分离。shmdt 函数接受一个参数即共享内存的起始地址。成功分离共享内存后进程将无法再访问共享内存中的数据。 删除共享内存段使用 shmctl 函数删除共享内存段。shmctl 函数接受三个参数共享内存标识符、命令用于指定要执行的操作和一个结构体指针用于传递额外的参数。通过指定命令为 IPC_RMID可以删除共享内存段。
下面是一个简单的 C 语言示例展示了如何使用 System V 共享内存
#include stdio.h
#include stdlib.h
#include sys/ipc.h
#include sys/shm.hint main() {key_t key ftok(shared_memory_key, 1234); // 创建一个唯一的键值int shm_id shmget(key, sizeof(int), IPC_CREAT | 0666); // 创建共享内存段if (shm_id -1) {perror(shmget);return 1;}int *shared_data (int *)shmat(shm_id, NULL, 0); // 连接共享内存if (shared_data (int *)-1) {perror(shmat);return 1;}*shared_data 42; // 在共享内存中写入数据printf(Shared data: %d\n, *shared_data);shmdt(shared_data); // 分离共享内存shmctl(shm_id, IPC_RMID, NULL); // 删除共享内存段return 0;
}在上面的示例中我们使用 ftok 函数创建一个唯一的键值用于标识共享内存段。然后我们使用 shmget 函数创建一个大小为 sizeof(int) 的共享内存段并指定了创建标志 IPC_CREAT | 0666。接下来我们使用 shmat 函数将共享内存连接到进程的地址空间并将其强制转换为 int 类型的指针。然后我们可以通过指针访问共享内存中的数据。最后我们使用 shmdt 函数分离共享内存并使用 shmctl 函数删除共享内存段。
请注意System V 共享内存是一种底层的机制需要谨慎使用以确保正确的同步和互斥。在实际应用中你可能需要使用其他同步机制如信号量或互斥锁来保护共享内存的访问。
system V信号量 选学了解即可
信号量主要用于同步和互斥的下面先来看看什么是同步和互斥。 在多进程或多线程的环境中进程互斥Process Mutual Exclusion是指一种机制用于确保同时只有一个进程或线程可以访问共享资源以避免数据竞争和不一致的结果。
当多个进程或线程同时访问共享资源时可能会出现以下问题 竞态条件Race Condition多个进程或线程以不可预测的方式相互干扰导致结果的正确性无法保证。 数据不一致多个进程或线程对共享资源进行读写操作时可能会导致数据的不一致性破坏程序的正确性。
为了解决这些问题需要使用进程互斥机制来确保共享资源的互斥访问。常见的进程互斥机制包括使用互斥锁Mutex、信号量Semaphore和临界区Critical Section等。
互斥锁是一种最常见的进程互斥机制它提供了两个基本操作上锁Lock和解锁Unlock。当一个进程或线程需要访问共享资源时它会尝试上锁如果成功获取到锁则可以访问共享资源如果锁已经被其他进程或线程持有则该进程或线程会被阻塞直到锁被释放。
通过使用互斥锁可以确保同一时间只有一个进程或线程可以访问共享资源从而避免了竞态条件和数据不一致的问题。
需要注意的是进程互斥只是一种机制它并不能解决所有的并发问题。在设计并发程序时还需要考虑其他因素如死锁Deadlock和活锁Livelock等。
进程同步Process Synchronization是指在多个进程或线程之间协调和控制它们的执行顺序以确保它们按照一定的顺序和时间间隔执行。
在并发环境中多个进程或线程可能会同时执行并且彼此之间的执行速度和顺序是不确定的。这可能导致一些问题如竞态条件、数据不一致和死锁等。
进程同步的目的是通过使用同步机制来协调和控制进程或线程的执行以避免这些问题。常见的进程同步机制包括互斥锁、信号量、条件变量和屏障等。
以下是一些常见的进程同步场景 互斥访问共享资源多个进程或线程需要访问共享资源时通过使用互斥锁来确保同一时间只有一个进程或线程可以访问共享资源避免数据竞争和不一致的结果。 同步执行顺序在某些情况下需要确保多个进程或线程按照特定的顺序执行。例如一个进程可能需要等待另一个进程完成某个任务后才能继续执行。这可以通过使用信号量或条件变量来实现。 生产者-消费者问题在生产者-消费者问题中多个生产者进程和消费者进程共享一个有限的缓冲区。生产者进程将数据放入缓冲区而消费者进程从缓冲区中取出数据。需要确保生产者和消费者之间的操作同步以避免缓冲区溢出或下溢。这可以通过使用信号量来实现。
进程同步是并发编程中的重要概念它可以帮助解决并发执行时可能出现的问题确保程序的正确性和一致性。
总之以上是进程间通信的基本概念在以后的学习中会慢慢深入进程更加详细的知识。