1. 进程间的通信方法
1. 管道 pipe
管道也是一种文件,有两种方式:匿名管道和命名管道
管道文件大小一般为4K或64K
匿名管道:int fd[2]; if(pipe(fd) < 0),只有父子进程可以使用,单向数据通信,不需要考虑同步和互斥,父子进程退出时,管道就结束了,生命周期是进程。
命名管道:int mkfifo(const char* pathname, mode_t mode); 管道路径 访问权限umask
创建文件后,服务端和客户端分别使用open打开文件,只读或只写
管道缺点,是基于文件系统的,因此不管是度还是写,都要求访问磁盘系统进行I/O操作,速度很慢,不适合多client的结构
2. 消息队列 mgget, ipcs -q 可以查看消息队列的状态
消息队列是一个类似链表的结构存在于内核的内存中,进程结束之后,消息队列还可能存在
管道是数据流式存取,消息队列是数据块式存取
创建消息队列 int msgget(key_t key, int msgflg) 返回消息队列id
key标识一个消息队列, msgflg IPC_CREAT已存在就打开,不存在就创建
把id和path绑定,获取key key_t ftok(const char* pathname, int proj_id)
发送消息 int msgsnd(int msgid, const void* msgp, size_t msgsz, int msgflg)
接收消息 int msgrcv(int msgid, const void* msgp, size_t msgsz, int msgflg)
3. 信号 signal,可以通过kill -l查看所有的信号
信号可能由硬件产生、由进程发送、异常
信号由操作系统处理,信号不一定会被立即处理,会在适当的时候,比如中断返回,内核态返回用户态的时候。
没有0信号,没有32、33信号,31之前是普通信号,后面是实时信号
信号的三种处理方式:忽略,默认会终止一个进程,自定义处理
sighandler_t signal(int signum, sighandler_t handler) handler是一个函数指针
int raise(int signo) 只能给自己进程发送信号
int kill(pid_t pid, int signo) 给对应的pid发送signo信号
4. 信号量 semget
多用于进程间的同步和互斥
同步:是指在互斥的基础上,通过某种机制使访问者对资源能够有序访问。
互斥:是指某一个资源同一时刻只允许一个访问者对其进行访问,具有唯一性和排他性,但是互斥不能限制访问顺序,即互斥是无序的
创建信号量 int semget(key_t key, int nsems, int semflg)
PV操作 int semop(int semid, struct sembuf* sops, unsigned nsops)
5. 共享内存 shmget,通过ipcs -m 可以查看当前共享内存的状态
管道是文件,操作慢,消息队列创建有消耗,共享内存效率高,速度快
创建共享内存 int shmget(key_t key, size_t size, int shmflg)
连接共享内存 void* shmat(int shmid, const void* shmaddr, int shmflg)
断开共享你村 int shmdt(const void* shmaddr)
6. 共享文件映射 mmap
7. 套接字 socket
2. 线程和进程有什么区别
根本区别:进程是分配和管理资源的基本单位,线程是进程任务调度和执行的基本单位。
开销方面:每个进程都有独立的代码和数据空间,进程之间切换会有较大的开销,线程可以看做轻量级的进程,统一进程内的线程共享代码和数据空间,每个线程有自己独立的运行栈和程序计数器,线程之间切换开销小。
所处环境:操作系统中能同时运行多个进程,同一进程内可以有多个线程同时执行。
地址空间:同一进程的线程共享本进程地址空间,而进程之间则是独立的地址空间
资源拥有:同一进程内的线程共享本金诚的资源,如内存、I/O、CPU等,但是进程之间的资源是独立的
线程是处理器调度的基本单位,但进程不是
进程和线程都可以并发执行
优缺点:
线程执行开销小,但是不利于资源的管理和保护
进程执行开销大,但是能够很好的进行资源管理和保护
什么时候用多进程,什么时候用多线程?
需要频繁创建和销毁的用多线程,进程创建和销毁开销大
线程切换速度快,需要大量计算,操作耗时的任务时,切换频繁时,使用多线程
多机分布的用多进程,多核分布的用多线程
需要更稳定安全时,使用多进程,需要速度时,使用多线程
4. 线程同步方法有哪些
1. 互斥锁mutex
初始化 int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutex_attr* attr)
加锁 int pthread_mutex_lock(phtread_mutex_t* mutex) 还有trylock
解锁 int pthread_mutex_unlock(pthread_mutex_t* mutex)
销毁 int pthread_mutex_destroy(phtread_mutex_t* mutex)
2. 条件变量cond
初始化 int pthread_cond_init(pthread_cond_t* cond, pthread_cont_attr_t* attr)
等待条件成立 int pthread_cond_wait(cond, mutex)
激活条件变量 phtread_cond_signal phtread_cond_broadcast
销毁 phtread_cond_destroy
3. 信号量
初始化 sem_init(sem_t* sem, int pshare, unsigned int value)
等待 sem_wait(sem_t* sem)
释放 sem_post(sem_t* sem)
销毁 sem_destroy(sem_t* sem)
互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号量即可用于上锁,也能用于等待,因此可能导致更多的开销和复杂性
4. 读写锁
适合读次数比写次数多得多的情况,以读模式锁时,可以共享,以写模式锁住时意味着独占,所以读写锁又叫 共享-独占锁
5. 自旋锁spinlock
自旋锁是在多个CPU系统中,当一个CPU访问被自旋锁保护的临界区时,自旋锁被锁上,其他要访问这个临界区的CPU只能等待,直到前面的CPU访问完开锁。
自旋锁上锁后等待进程将进行忙等待而不是睡眠阻塞,忙等待浪费了处理时间,但是时间通常很短,1ms以下,信号量是让线程睡眠阻塞,
6. 屏障barrier
屏障是linux中协调多个线程并行工作的同步机制,屏障允许每个线程等待,直到所有合作线程达到某一点,然后继续从该点执行, pthread_join是一种屏障,但只允许一个线程等待。
pthread_barrier_int
pthread_barrier_wait
5. 死锁
死锁是两个以上的进程在执行过程中, 由于竞争资源或彼此通信造成的一种阻塞,若无外力作用,他们都无法继续执行下去
死锁产生的4个必要条件:
- 互斥条件:进程要求对所分配的资源进行排他控制,在一段时间内某资源仅能被一个进程占用
- 请求和保持:当进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时自己释放
- 环路等待条件:在发生死锁时,必然存在一个进程-资源的环形链
怎么防止死锁
资源一次性分配:一次性分配所有资源,这样就不会再有请求了(破坏请求)
只要有一个资源得不到分配,就不给这个进程分配其他资源(破坏保持)
可剥夺资源:某进程获得了部分资源,但得不到全部资源,释放已有资源(破坏不可剥夺)
资源有需分配:系统给每类资源编号,每个进程按编号递增顺序请求资源,释放相反(破坏环路等待)