Linux系统中的进程间通信方式主要以下几种:
同一主机上的进程通信方式
网络主机间的进程通信方式
各自的特点如下:
- 管道(PIPE):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系(父子进程)的进程间使用。另外管道传送的是无格式的字节流,并且管道缓冲区的大小是有限的(管道缓冲区存在于内存中,在管道创建时,为缓冲区分配一个页面大小)。
- 有名管道 (FIFO): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号(Signal): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 信号量(Semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列(Message Queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(Shared Memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 套接字(Socket): 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。
线程间通信机制:
实例:
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
typedef void* (*fun)(void*);
int x=1,y=4;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread1(void*);
void* thread2(void*);
int main(int argc, char** argv)
{
printf("enter main!\n");
pthread_t tid1, tid2;
int rc1=0, rc2=0;
rc2 = pthread_create(&tid2, NULL, thread2, NULL);
if(rc2 != 0)
printf("%s: %d\n",__func__, strerror(rc2));
rc1 = pthread_create(&tid1, NULL, thread1, &tid2);
if(rc1 != 0)
printf("%s: %d\n",__func__, strerror(rc1));
sleep(1);
printf("leave main!\n");
exit(0);
}
void* thread1(void* arg)
{
printf("enter thread1\n");
printf("this is thread1: x= %d,y=%d. thread id is %u\n",x,y, (unsigned int)pthread_self());
pthread_mutex_lock(&mutex);
x+=y;
if(x>y)
pthread_cond_signal(&cond);
printf("this is thread1: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self());
pthread_mutex_unlock(&mutex);
pthread_join(*(pthread_t*)arg, NULL);
printf("leave thread1\n");
pthread_exit(0);
}
void* thread2(void* arg)
{
printf("enter thread2\n");
printf("this is thread2: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self());
pthread_mutex_lock(&mutex);
while(x<=y)
pthread_cond_wait(&cond, &mutex);
x-=y;
printf("this is thread2: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self());
pthread_mutex_unlock(&mutex);
printf("leave thread2\n");
pthread_exit(0);
}
有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情况下的同步使用互斥锁
是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。条件变量(Condition Variable)是线程间的一种同步机制,提供给两个线程协同完成任务的一种方法,使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量的测试一般是用互斥量来保护的,用来确保每一时刻只有一个线程能够改变条件变量,如果条件为假,线程通常会基于条件变量而阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:
- 唤醒
- 再次获取互斥锁
- 重新评估条件
在以下情况下,条件变量可用于在进程之间同步线程:
- 线程是在可以写入的内存中分配的
- 内存由协作进程共享
Condition Variable用pthread_cond_t类型的变量表示,和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果ConditionVariable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。
条件变量的相关函数如下:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
//Both return: 0 if OK, positive Exxx value on error
pthread_cond_wait 用于等待某个特定的条件为真,一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回
注意:3个操作是原子性的操作,之所以一开始要释放Mutex,是因为需要让其他线程进入临界区去更改条件,或者也有其他线程需要进入临界区等待条件。
pthread_cond_signal 用于通知阻塞的线程某个特定的条件为真了。在调用者两个函数之前需要声明一个 pthread_cond_t 类型的变量,用于这两个函数的参数。
为什么条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的呢?因为“某个特性条件”通常是在多个线程之间共享的某个变量。互斥锁允许这个变量可以在不同的线程中设置和检测。
通常, pthread_cond_wait 只是唤醒等待某个条件变量的一个线程。如果需要唤醒所有等待某个条件变量的线程,需要调用:
int pthread_cond_broadcast (pthread_cond_t * cptr);
默认情况下面,阻塞的线程会一直等待,知道某个条件变量为真。如果想设置最大的阻塞时间可以调用:
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);
如果时间到了,条件变量还没有为真,仍然返回,返回值为ETIME。
二、条件变量使用规范
(一)、等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
(二)、给条件发送通知代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。考虑以下情况:
1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.
其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。