目录
信号量(线程的同步 )
- 同步 ===》有 一定先后顺序的 对资源的排他性访问。
- 互斥 ===》在多线程中对临界资源的排他性访问。
linux下的线程同步 ===》信号量机制 ===》semaphore.h posix sem_open();
信号量的分类:
- 信号无名量 ==》线程间通信
- 有名信号量 ==》进程间通信
框架:
- 信号量的定义 sem_t sem //造了一类资源
- 信号量的初始化 sem_init
- 信号量的PV操作 (核心) sem_wait()/ sem_post()
- 信号量的销毁。 sem_destroy
(1)信号量的定义(semaphore):
sem_t | sem; |
信号量的类型 | 信号量的变量 |
sem_t sem_w;
sem_t sem_r;
(2)信号量的初始化:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:将已经定义好的信号量赋值。
参数:sem 要初始化的信号量
pshared = 0 ;表示线程间使用信号量;pshared!=0 ;表示进程间使用信号量
value 信号量的初始值,一般无名信号量,都是二值信号量0 /1;0 表示红灯,进程暂停阻塞;
1 表示绿灯,进程可以通过执行
返回值:成功 0; 失败 -1;
sem_init(&sem_w, 0, 1);
sem_init(&sem_r, 0, 0);
(3)信号量的PV 操作
- P ===》申请资源===》申请一个二值信号量 sem_wait();
- V ===》释放资源===》释放一个二值信号量 sem_post();
int sem_wait(sem_t *sem); //P操作
功能:
判断当前sem信号量是否有资源可用。
如果sem有资源(==1),则申请该资源,程序继续运行
如果sem没有资源(==0),则线程阻塞等待,一旦有资源
则自动申请资源并继续运行程序。
注意:sem 申请资源后会自动执行 sem = sem - 1;
参数:sem 要判断的信号量资源
返回值:成功 0 ;失败 -1
int sem_post(sem_t *sem); //V操作
功能:
函数可以将指定的sem信号量资源释放
并默认执行,sem = sem+1;
线程在该函数上不会阻塞。
参数:sem 要释放资源的信号量
返回值:成功 0
失败 -1;
(4)信号量的销毁
int sem_destroy(sem_t *sem);
功能:使用完毕将指定的信号量销毁
参数:sem要销毁的信号量
返回值:成功 0;失败 -1;
练习;用多线程程序设计一个火车票售票系统,
要求:1. 至少有两个售票窗口,每个售票窗口
2. 不能重复买票,将100张车票均匀的从两个窗口卖出即可。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <semaphore.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int ticket = 100;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#if 0
void *doWin1(void *arg)
{
int i = 0;
while (ticket)
{
pthread_mutex_lock(&mutex);
ticket--;
printf("win1 sell ticket = %d\n",100-ticket);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
#endif
void *doWin1(void *arg)
{
int i = 0;
pthread_mutex_lock(&mutex);
while (ticket)
{
ticket--;
printf("win1 sell ticket = %d\n",100-ticket);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void *doWin2(void *arg)
{
int i = 0;
pthread_mutex_lock(&mutex);
while (ticket)
{
ticket--;
printf("win2 sell ticket = %d\n",100-ticket);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,doWin1,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,doWin2,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
进程间的通信
通信方式分类:
(1)不同主机之间(基于内存)
1)古老的通信方式:管道(无名管道、有名管道);信号;
2)IPC对象通信(改进):【a:消息队列(用的相对少,这里不讨论)】;【b:共享内存//最高效】;【c:信号量集//信号量】;
(2)不同主机之间(socket 网络部分);
管道
- 无名管道 ===》pipe ==》只能给有亲缘关系进程通信
- 有名管道 ===》fifo ===》可以给任意单机进程通信(同一主机内)
特性:
- 管道是半双工的工作模式
- 所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE* 数据流 --- FIFO(first in first out)
- 管道是特殊文件,读写使用文件IO。 fgets,fread,fgetc,open,read,write,close;;
无名管道(pipe函数):
特性:【亲缘关系进程使用】;【有固定的读写端】;
流程:
创建并打开管道: pipe函数
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:
- pipefd[0] ==>无名管道的固定读端//0 -- 标准输入,
- pipefd[1] ==>无名管道的固定写端//1 -- 标准输出
返回值:成功 0;失败 -1;
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("pipe fail");
return -1;
}
printf("fd[0] = %d\n", fd[0]);
printf("fd[1] = %d\n", fd[1]);
char buf[] = "hello world";
write(fd[1], buf, strlen(buf));
char s[100] = {0};
read(fd[0], s, sizeof(s));
printf("s = %s\n", s);
return 0;
}
注意:无名管道的架设应该在fork之前进行
管道的读写规则:
读端:pipefd[0] | 写端:pipefd[1] | 结果 |
存在 | 存在 | 写管道:管道空:可以写数据 管道满:会造成-->写阻塞 |
不存在 | 存在 | 写管道:系统会给进程发一个信号SIGPIPE(管道破裂) |
存在 | 存在 | 读管道:管道空,读不到数据,这时会造成读操作阻塞 |
存在 | 不存在 | 读管道:如果管道中有数据,则读取这些数据;如果没有数据,读操作不阻塞,立即返回! |