参考资料:
http://blog.youkuaiyun.com/alexlee1986/article/details/21227417
http://blog.youkuaiyun.com/alexlee1986/article/details/21227417
《UNIX网络编程》卷1、2
进程间通信
一、进程
程序的执行实例被称为进程,程序都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道)组成。由于不同的进程运行在不同的内存空间中,其中一个进程对于变量的修改另一方是无法感知的,因此进程之间的消息不能通过变量或其他数据结构直接进行,只能通过进程间通信来完全。
二、进程间通信
进程间通信是指不同进程间进行数据共享和数据交换。
三、进程通信的分类
进程的通信机制主要有:管道、有名管道,消息队列、信号量、共享空间、信号、套接字
(1)管道
管道是Unix系统IPC的最古老形式,可分为无名管道和有名管道。
a.无名管道
int pipe(int fd[2])
read(fd[0], buf, 0);
无名管道是一种半双工的通信方式,数据只能单向流动。
只能在具有亲缘关系的进程间使用,一般是指父子关系或兄弟关系(因为没有名字)。
没有名字且大小受限,传输的是无格式的流,所以两进程通信时必须约定好数据通信的格式。
管道对于通信两端的进程而言,实际相当于一个特殊的文件,在创建管道时,系统为管道分配了一个页面作为数据缓冲区,进程对这个数据缓冲区进行读写,以此来完成通信。
半双工通信:其中一个进程只能写,另一个只能读。写进程在管道缓冲区的尾部写数据,读进程从管道缓冲区的头部读数据。
b.有名管道
int mkfifo(const char * pathname,mode_t mode);
类似于管道,是一个单向(半双工)数据流。
不同于管道的是,有名管道是一个设备文件,每个有名管道有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。
readfd=open(pathname, O_RDONLY, 0);
writefd=open(pathname, O_WRONLY, 0);
(2)信号
信号是软件中断,提供了一种处理异步事件的方法。
产生信号的时间对进程而言是随机出现的,操作系统通过信号来通知某一进程发生了某一种预定好的事件;
接受到信号的进程可以选择不同的方式处理改信号:a.忽略该信号 b.自定义改信号的处理函数 c.执行系统默认动作
https://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
//函数说明
// sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。
//参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
struct sigaction {
void (*sa_handler)(int);//和signal()的参数handler相同,代表新的信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
int sa_flags;//设置其他标志
void (*sa_restorer)(void);
}
(3)信号量
信号量是一个计数器,可以用来控制多个进程对共享数据对象的访问。
当信号>=0,表示可以供并发进程访问的的资源数目;当信号量<0,表示因等待资源而阻塞的进程数。
int sem_wait(sem_t *sem);//测试所指定信号量的值,如果该值大于0,那么就将它减1,函数随后返回。当所指指定信号量已经是0时,则进程投入睡眠。
int sem_post(sem_t *sem);//当一个线程使用完某个信号量,则调用sem_post,把指定的信号量的值加1,然后唤醒正在等待该信号量的值变为整数的任意线程。
(4)消息队列
消息队列是消息的链接表,由消息队列标示符标识。
Unix允许不同进程将格式化的数据流以消息队列的形式发送给任意进程。
通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序。
消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)共享内存
共享内存允许两个或多个进程共享一个给定的存储区。
因为数据不需要再客户进程和服务器进程之间复制,因此是最快的一种IPC。
往往和其他通信机制,如信号量配合使用,来实现进程间的同步与通信。
在使用共享内存区之前,必须通过系统函数将其映射到进程空间。两个不同的进程A、B共享内存,即同一个物理内存块被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。
posix共享内存区涉及一下两个步骤要求:
(1)指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象。
(2)调用mmap把这个共享内存区映射到调用进程的地址空间。
由于多个进程共享同一块内存区域,必然需要某种同步机制,如互斥锁和信号量。
优:效率高,因为 (1)进程可以直接读写内存,而不需要然和数据的拷贝。(2)并不是写少量数据就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件共享内存区中的内容往往是在解除映射时才写回文件。
对于管道和消息队列等同喜方式,需要在内核和用户空间进行四次数据拷贝;
eg.对于客户-服务器:
a.服务器从输入文件读,该文件数据由内核读入自己的内存空间(文件到进程缓冲区);
b.服务器往一个管道、FIFO或消息队列以一条消息的形式写入这些数据(进程到内核);
c.客户从该IPC通道读出这些数据(内核到进程);
d.将这些数据从write的第一个参数指定的客户缓冲区复制到输出文件(进程缓冲区到文件)。
共享内存主需要两次:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。
共享内存http://blog.youkuaiyun.com/ljianhui/article/details/10253345
共享进程内存同步
1、信号量
2、无锁队列
共享内存越界 http://blog.youkuaiyun.com/turkeyzhou/article/details/8608662
(6)套接字
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。
套接字是通信端点的抽象。正如使用文件描述符访问文件,应用程序使用套接字描述符访问套接字。套接字描述符在Unix系统中被当做是一种文件描述符。
Linux进程间通讯的几种方式的特点和优缺点,和适用场合
线程间通信
一、线程信的分类
线程的通信机制主要有:互斥锁、信号量、条件变量
(1)互斥量:包括互斥锁、条件变量、读写锁
a.互斥锁:指代相互排斥,是最基本的同步形式。互斥锁用于保护临界区,以保证任何时刻只有一个进程在执行其中的代码。
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
b.条件变量:是一种同步机制,允许线程挂起,直到共享数据的某些条件得到满足,条件变量的基本操作由 a. 触发条件 b. 等待条件 ;c.挂起线程知道其他线程触发条件
pthread_cond_wait(pthread_cond_t * cptr, pthread_t mptr);
//如果在锁住该互斥锁期间该计数器的值为0,就调用pthread_cond_wait进入睡眠,该函数原子的执行以下两个动作:
//(1)给互斥锁nready.mutex解锁
//(2)把调用线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_signal
//pthread_cond_wait在返回前重新给互斥锁mutex加锁
//!!当pthread_cond_wait返回时,可能发生虚假的唤醒:期待的条件尚不成立时的唤醒,因此要再次测试响应太偶见成立与否
pthread_cond_signal(pthread_cond_t *cptr);//唤醒可能正在等待其值非0的任意线程
条件变量要由互斥变量进行保护,线程在改变状态之前必须首先锁住互斥量,以免出现竞争条件——一个线程预备等待一个条件变量,当在进入等待之前,另一个线程刚好触发条件
使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
c.读写锁:允许多个线程同时读共享数据,而对写操作是互斥的
//Description:这个程序是测试pthread_mutex对多线程上锁。
#include <iostream>
#include <pthread .h>
using namespace std;
pthread_mutex_t mutex; //~ 创建互斥锁mutex
int share = 0; //~ 共享数据
string sharestr="a";
//~ 线程入口
void * thread_function(void *arg)
{
const int N = 1000000;
for(int i = 0; i < N; ++i)
{
//~ 访问共享数据
pthread_mutex_lock(&mutex);
share++;
usleep(1000);
if(i%100000==0)
sharestr.append("b");
pthread_mutex_unlock(&mutex);
}
}
int
main()
{
pthread_mutex_init(&mutex, NULL); //~ 初始化互斥锁mutex
pthread_t thread_id;
void *exit_status;
pthread_create(&thread_id, NULL, thread_function, NULL); //~ 创建新线程
for(int i = 0; i < 10; ++i)
{
usleep(10000); //~ 延时10ms
//~ 访问共享数据
pthread_mutex_lock(&mutex);
cout<<share<<endl;
cout<<sharestr<<endl;
pthread_mutex_unlock(&mutex);
}
cout<<"waiting pthread finished"<<endl;
pthread_join(thread_id, &exit_status); //~ 等待线程结束
cout<<"destroy pthread mutex"<<endl;
pthread_mutex_destroy(&mutex); //~ 销毁互斥锁
cout<<"destroy pthread mutex finished"<<endl;
return 0;
}
c++ pthread_mutex.cpp -pthread
$ ./a.out
218555
abbb
437120
abbbbb
656183
abbbbbbb
874736
abbbbbbbbb
1000000
abbbbbbbbbb
1000000
abbbbbbbbbb
1000000
abbbbbbbbbb
1000000
abbbbbbbbbb
1000000
abbbbbbbbbb
1000000
abbbbbbbbbb
waiting pthread finished
destroy pthread mutex
destroy pthread mutex finished
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用。
(2)信号量:
信号量S是一种特殊的变量,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。只能进行两种操作等待和发送信号,P操作和V操作
P操作申请资源:(1)S=S-1;
(2)如果S-1后仍大于等于0,则进程继续执行;
(3)若S-1后<0,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作释放资源:(1)S=S+1;
(2)若S+1后>0,则进程继续执行;
(3)若S+1后<=0,则从该信号的等待队列中唤醒一个等待进程,然后返回原进程继续执行或转入进程调度
(3)事件:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。event_fd
注:
1,S大于0那就表示有临界资源可供使用,为什么不唤醒进程?
S大于0的确表示有临界资源可供使用,也就是说这个时候没有进程被阻塞在这个资源上,所以不需要唤醒。
2,S小于0应该是说没有临界资源可供使用,为什么还要唤醒进程?
V原语操作的本质在于:一个进程使用完临界资源后,释放临界资源,使S加1,以通知其它的进程,这个时候如果S<0,表明有进程阻塞在该类资源上,因此要从阻塞队列里唤醒一个进程来“转手”该类资源。比如,有两个某类资源,四个进程A、B、C、D要用该类资源,最开始S=2,当A进入,S=1,当B进入S=0,表明该类资源刚好用完, 当C进入时S=-1,表明有一个进程被阻塞了,D进入,S=-2。当A用完该类资源时,进行V操作,S=-1,释放该类资源,因为S<0,表明有进程阻塞在该类资源上,于是唤醒一个。
3,如果是互斥信号量的话,应该设置信号量S=1,但是当有5个进程都访问的话,最后在该信号量的链表里会有4个在等待,也是说S=-4,那么第一个进程执行了V操作使S加1,释放了资源,下一个应该能够执行,但唤醒的这个进程在执行P操作时因S<0,也还是执行不了,这是怎么回事呢?
当一个进程阻塞了的时候,它已经执行过了P操作,并卡在临界区那个地方。当唤醒它时就立即进入它自己的临界区,并不需要执行P操作了,当执行完了临界区的程序后,就执行V操作。
4,S的绝对值表示等待的进程数,同时又表示临界资源,这到底是怎么回事?
当信号量S小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目.S大于0时表示可用的临界资源数。注意在不同情况下所表达的含义不一样。当等于0时,表示刚好用完。
信号量、互斥锁和条件变量之间的区别:
(1)互斥锁总是由给它上锁的线程解锁,信号量的挂出却不必由执行过它的等待操作的同一线程执行。
(2)互斥锁要么被锁住,要么被解开(二值状态,类似于二值信号量)
(3)既然信号量有一个与之关联的状态(它的计数值),那么信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。