目录
1.Linux进程间通信方法
- 管道
- 有名管道
- 消息队列
- 信号量
- 共享内存
2. 管道
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
- 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2.1 shell中的管道
下面命令用创建了aux到grep的管道|,筛选出进程中包含jk字符串的信息
ps - aux -aux 显示所有包含其他使用者的行程
ps aux | grep jk
2.2 无名管道 编程
无名管道的特点是:使用其通信的进程必须有亲属关系,例如父子 兄弟进程
使用头文件:#include <unistd.h>
2.3 无名管道代码
这段代码需要注意一个问题,父子进程运行的先后顺序在不同设备上是不一定的,设计的代码应当不受其执行先后顺序的影响,但是下列代码是要先执行父进程,在执行子进程才行,后续还可以改进!!!
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
char str[]="hello world,my pipe!";
int main()
{
//创建管道,pid[0] [1] 分别为读和写
int fd[2];//定义两个文件描述符
int result=pipe(fd);
int *write_fd=&fd[1],*read_fd=&fd[0];//绑定描述副
int nbytes;
char read_buff[100]={""};
if(result==-1)
cout<<"fail to create pipe!"<<endl;
pid_t pid;//创建进程号
result=-1;
pid=fork();//创建两个进程
if(pid==-1)
cout<<"fail to fork!"<<endl;
if(0==pid)//执行子进程代码,接受
{
close(*write_fd);
nbytes=read(*read_fd,read_buff,sizeof(read_buff));
printf("the parent read %d bytes:%s",nbytes,read_buff);
return 0;
}
else//父进程 发送
{
close(*read_fd);
result=write(*write_fd,str,strlen(str));
cout<<"the child send the string "<<str<<endl;
}
while(1);
return 0;
}
2.4 有名管道
SIGCHLD
产生原因 siginfo_t代码值
1,子进程已终止 CLD_EXITED
2,子进程异常终止(无core) CLD_KILLED
3,子进程异常终止(有core) CLD_DUMPED
4,被跟踪子进程以陷入 CLD_TRAPPED
5,子进程已停止 CLD_STOPED
5,停止的子进程已经继续 CLD_CONTINUED
描述
在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程。按系统默认将忽略此信号。如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。信号的捕捉函数中通常调用wait函数以取得进程ID和其终止状态。
代码参考(待测试)http://blog.chinaunix.net/uid-20498361-id-1940238.html
2.5 关于fork函数
fork(函数)
UNIX及类UNIX(UNIX-like)系统中的分叉函数。返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程标记;否则,出错返回-1。
fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。
值得注意的是,fork( void )有三种不同的返回值
1、在父进程中,fork返回新创建的子进程的PID
2.、在子进程中,fork返回0
3、如果出现错误,fork返回一个负值
#include <unistd.h>
#include <iostream>
using namespace std;
int main(){
pid_t pid;
pid=fork();
if(pid==0){
cout<<"子进程fork()返回值="<<pid<<endl;//子进程中,fork()返回0
cout<<"子进程pid="<<getpid()<<endl;//子进程中的pid号
}else{
cout<<"父进程fork()返回值="<<pid<<endl;//父进程中,fork()返回子进程的id
cout<<"父进程pid="<<getpid()<<endl;//父进程中的pid号
}
return 0;
}
输出结果为:
父进程fork()返回值=6819
父进程pid=6818
子进程fork()返回值=0
子进程pid=6819
总结:fork()的返回值可以理解为当前进程的子进程id,例如父进程有子进程,那么返回值为子进程id;子进程本身没有子进程,因此fork返回值为0;
3. 消息队列
消息队列有以下特点:
- 消息队列提供了一种在两个不相关的进程之间传递数据的简单有效的方法,与命名管道相比,消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难;
- 消息队列提供了进程间发送和接收数据块的方法,每个数据块都被认为含有一个类型,接收进程可以独立的接收含有不同类型值得数据块
与管道比,优点:
1)独立于发送和接收进程存在,避免了命名管道的同步和阻塞问题;
2)消息队列提供了方法来提前查看紧急消息
与管道比,相同点:
1)每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度有一个上限
2)linux系统有两个宏定义MSGMAX——定义了消息的最大长度,和MSGMNB——定义了队列的最大长度
关键函数:
创建消息队列
int msgget(key_t key,int msgflg);
//例如:
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);//获取队列标识符
0666:
从左向右:
- 第一位:表示这是个八进制数 000
- 第二位:当前用户的经权限:6=110(二进制),每一位分别对就 可读,可写,可执行,,6说明当前用户可读可写不可执行
- 第三位:group组用户,6的意义同上
- 第四位:其它用户,每一位的意义同上,0表示不可读不可写也不可执行
添加消息到队列中
int msgsnd(int msgid,const void *msg_ptr,size_t msg_sz,int msgflg);
从消息队列中获取消息
int msgrcv(int msgid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);
//例如
struct my_msg_st {
long int my_msg_type;//消息类型
char some_text[BUFSIZ];//消息缓存
};
struct my_msg_st some_data;
msgrcv(msgid, (void *)&some_data, BUFSIZ,msg_to_receive, 0) //接收消息
消息队列控制策略
以下参考例子:
//Msg1.c接收消息
/* Here's the receiver program. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
struct my_msg_st {
long int my_msg_type;//消息类型
char some_text[BUFSIZ];//消息缓存
};
int main()
{
int running = 1;
int msgid;
struct my_msg_st some_data;
long int msg_to_receive = 0;//接收数据计数
/* First, we set up the message queue. */
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);//获取队列标识符
if (msgid == -1) {
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
/* Then the messages are retrieved from the queue, until an end message is encountered.
Lastly, the message queue is deleted. */
while(running) {
if (msgrcv(msgid, (void *)&some_data, BUFSIZ,//接收消息
msg_to_receive, 0) == -1) {
fprintf(stderr, "msgrcv failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s", some_data.some_text);
if (strncmp(some_data.some_text, "end", 3) == 0) {//结束循环
running = 0;
}
}
if (msgctl(msgid, IPC_RMID, 0) == -1) {
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
//发送
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
//默认BUFSIZE=8192
#define MAX_NUM 512;
struct my_msg_rev {
long int my_msg_type;//消息类型
char some_text[BUFSIZ];//消息缓存
};
int main()
{
struct my_msg_rev some_data;
int running=1;
char buffer[BUFSIZ];
//创建消息队列
int msgid=msgget((key_t)1234,0666|IPC_CREAT);
while(running){
some_data.my_msg_type=1;
printf("输入text,end为结束\n");
fgets(buffer,BUFSIZ,stdin);//输入字符串
strcpy(some_data.some_text,buffer);//‘\0’结束
if(msgsnd(msgid,&some_data,(size_t)BUFSIZ,0)==-1){
printf("msgsend is error! \n");
exit(EXIT_FAILURE);//退出
}
if(strcmp(buffer,"end")==0){
running=0;
}
}
return 0;
}
4. 信号量
5. 共享内存
https://www.cnblogs.com/fangshenghui/p/4039720.html
6.线程
线程同步的方式有多种:
- pthread_join的等待执行结束
- 互斥量
- 条件量
- 自旋锁
- 读写锁
6.1 线程函数
#include <pthread.h> int pthread_create(pthread_t * tidp, const pthread_attr_t *attr, void *(*start_rtn) (void *), void *arg);
成功返回0,错误返回错误编号
#include <pthread.h> void pthread_exit(void *rval_ptr); // 线程终止 //pthread_exit用于强制退出一个线程(非执行完毕退出),一般用于线程内部。
线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
- 线程只是从启动过程中退出,返回值是线程的退出码
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit
#include <pthread.h> int pthread_join(pthread_t thread, void **rval_ptr); // 返回:成功返回0,出错返回错误代码 //thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
用于等待线程结束,并用rval_ptr获取线程结束的返回值
互斥量的操作函数
#include <pthread.h>
//互斥量初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
//摧毁互斥量,互斥量使用完毕再调用!!
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 两个函数返回值,成功返回0,否则返回错误码
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);//如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 成功返回0,否则返回错误码
读写锁
读写锁和互斥体类似,不过读写锁有更高的并行性,互斥体要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。而读写锁可以有3个状态,读模式下锁住状态,写模式下锁住状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。读写锁适合对数据结构读的次数远大于写的情况。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。
当前状态 可执行的操作 不可执行的操作 读加锁 所有试图读加锁的操作可执行 写加锁操作不可执行 写加锁 对齐加锁的线程可以获得访问权 其他试图对其加锁的线程都被阻塞 不加锁 ALL ALL
条件变量
条件变量是线程可用的一种同步机制,条件变量给多个线程提供了一个回合的场所,条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。条件变量本事是由互斥体保护的,线程在改变条件状态之前必须首先锁住互斥量,其他线程在获取互斥量之前就不会觉察到这种变化,因为互斥量必须锁定之后才改变条件。
#include<pthread.h> pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_destroy(pthread_cont_t *cond); // 成功返回0,否则返回错误码 使用条件变量前调用pthread_cond_init初始化,使用完毕后调用pthread_cond_destroy做清理工作。除非需要创建一个具有非默认属性的条件变量,否则pthread_cond_init函数的attr参数可以设置为NULL。 #include<pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 成功返回0,否则返回错误码
条件变量 传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait函数返回时,互斥量再次被锁住。
pthread_cond_broadcast用广播的形式唤醒所有等待条件变量的线程。pthread_cond_signal用于唤醒一个等待条件变量的线程,至于哪个线程被唤醒,取决于线程的优先级和调度机制。有时候需要唤醒一个指定的线程,但pthread没有对该需要提供解决方法。可以间接实现该需求:定义一个能够唯一表示目标线程的全局变量,在唤醒等待条件变量的线程前先设置该变量为目标线程,然后以广播形式唤醒所有等待条件变量的线程,这些线程被唤醒后都检查改变量是否是自己,如果是就开始执行后续代码,否则继续等待。
自旋锁
自旋锁和互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态,自旋锁可用于下面的情况:锁被持有的时间短,并且线程不希望再重新调度上花费太多的成本。自旋锁通常作为底层原语用于实现其他类型的锁。根据他们所基于的系统架构,可以通过使用测试并设置指令有效地实现。当然这里说的有效也还是会导致CPU资源的浪费:当线程自旋锁变为可用时,CPU不能做其他任何事情,这也是自旋锁只能够被只有一小段时间的原因。
#include <pthread.h> int pthread_spin_init(pthread_spinlock_t *lock, int pshared); int pthread_spin_destroy(pthread_spinlock_t *lock); pshared参数表示进程共享属性,表明自旋锁是如何获取的,如果它设为PTHREAD_PROCESS_SHARED,则自旋锁能被可以访问锁底层内存的线程所获取,即使那些线程属于不同的进程。否则pshared参数设为PTHREAD_PROCESS_PROVATE,自旋锁就只能被初始化该锁的进程内部的线程访问到。 #include <pthread.h> int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock);
如果自旋锁当前在解锁状态,pthread_spin_lock函数不要自旋就可以对它加锁,试图对没有加锁的自旋锁进行解锁,结果是未定义的。需要注意,不要在持有自旋锁情况下可能会进入休眠状态的函数,如果调用了这些函数,会浪费CPU资源,其他线程需要获取自旋锁需要等待的时间更长了。
https://www.cnblogs.com/wujing-hubei/p/5222013.html
https://www.cnblogs.com/luoxn28/p/6087649.html
https://www.cnblogs.com/yangang92/p/5679641.html