线程:
回顾线程(一):
1.线程间通信问题
线程间共享同一个资源(临界资源)
互斥:
排他性访问
linux系统 -- 提供了Posix标准的函数库 -- 互斥量(互斥锁)
原子操作:---
机制:
加锁 -- 解锁
锁 --- 操作系统 --- (实现的机制,需要操作系统来
| - 加锁 -- 用户态 -- 切换到(耗时) -- 内核态 -- 获得了 -- 内核态 -- 用户态 -- 解锁 |
函数:
pthread_mutex_t mutex;
pthread_mutex_init();
pthread_mutex_lock();
pthread_mutex_trylock(); // 尝试获得锁,若没获得则返回非0值。
pthread_mutex_unlock();
pthread_mutex_destroy();
线程的同步:
同步 ==》有 一定先后顺序的 对资源的排他性访问。
要同步的原因:互斥锁可以控制排他访问但没有次序。
信号量 --- 实现线程间的同步.
来源 生活 --- 交通信号灯
信号量的分类:
1、无名信号量 ==》线程间通信
2、有名信号量 ==》进程间通信
同步机制:
信号量(个数) --- 反映的是资源的数量
考虑的时候,站在使用这的角度考虑
站在a的角度考虑。。。。。。
框架:
1. 信号量的定义 sem_t sem //造了一类资源
2. 信号量的初始化 sem_init
3. 信号量的PV操作 (核心) sem_wait()/ sem_post()
4. 信号量的销毁。 sem_destroy
信号量函数:
1、定义
sem_t 名字;
2、初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
将已经定义好的信号量赋值。
参数:
@sem 要初始化的信号量
@pshared
pshared = 0 ;表示线程间使用信号量(一般填这个)
!=0 ;表示进程间使用信号量
@value:
信号量的初始值,一般无名信号量(一开始的资源的个数)
都是二值信号量,0 1
0 表示红灯,进程暂停阻塞
1 表示绿灯,进程可以通过执行
也可以是多个,变成计数信号量
返回值:
成功 0,失败 -1;
3、PV操作
int sem_wait(sem_t *sem); //p操作
功能:
判断当前sem信号量是否有资源可用。
如果sem有资源(==1),则申请该资源,程序继续运行
如果sem没有资源(==0),则线程阻塞等待,一旦有资源
则自动申请资源并继续运行程序。
消耗了这个sem,就没有了
注意:sem 申请资源后会自动执行 sem = sem - 1;
参数:
@sem 要判断的信号量资源
返回值:
成功 0 ,失败 -1
int sem_post(sem_t *sem); //V操作
功能:
函数可以将指定的sem信号量资源释放
并默认执行,sem = sem+1;
线程在该函数上不会阻塞。
产生了这个sem就有了,可以由wait(sem)接受去消耗
参数:
@sem 要释放资源的信号量
返回值:
成功 0,失败 -1;
4、销毁
int sem_destroy(sem_t *sem);
练习: hello world
#include<stdio.h>
#include<semaphore.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>
sem_t sem_h;
sem_t sem_w;
void *do_hello(void *arg)
{
while(1)
{
sem_wait(&sem_h);
printf("hello ");
sem_post(&sem_w);
}
return NULL;
}
void *do_world(void *arg)
{
while(1)
{
sem_wait(&sem_w);
printf("world\n");
sem_post(&sem_h);
}
return NULL;
}
typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
int i;
pthread_t tid[i];
threadF_t pFunc[2] = {do_hello,do_world};
sem_init(&sem_h,0,1);
sem_init(&sem_w,0,0);
for(i = 0;i < 2;++i)
{
int ret = pthread_create(&tid[i],NULL,pFunc[i],NULL);
if(ret != 0)
{
errno = ret;
perror("pthread_create fail");
exit(EXIT_FAILURE);
}
}
sem_destroy(&sem_h);
sem_destroy(&sem_w);
pthread_detach(tid[0]);
pthread_detach(tid[1]);
printf("---main----exit\n");
pthread_exit(NULL);
return 0;
}
进程间的通信方式:
三大类:
1.同主机 ---- 基于内存的
古老的通信方式
//管道 ----
无名管道
有名管道
//信号
IPC对象通信(改进)
消息队列(用的相对少,这里不讨论)
共享内存(*) //最高效
信号量集() //信号量
2.
//不同主机 、多台主机
socket //网络部分
//同一主机
2.1、古老的通信方式
管道:
无名管道
有名管道
信号
2.2、IPC对象通信 system v BSD suse fedora kernel.org
消息队列(用的相对少,这里不讨论)
共享内存(*) //最高效
信号量集() //信号量
//不同主机
3、socket通信
网络通信
1、pipe 无名管道
使用框架:
创建管道 ==》读写管道 ==》关闭管道
1、无名管道 ===》管道的特例 ===>pipe函数
特性:
1.1 亲缘关系进程使用
1.2 有固定的读写端
流程:
创建并打开管道: pipe函数
函数:
#include <unistd.h>
int pipe(int pipefd[2]);
int pipe(int *pipefd);
int fd[2];
功能:创建并打开一个无名管道
参数: @pipefd[0] ==>无名管道的固定读端//0 -- 标准输入
@pipefd[1] ==>无名管道的固定写端//1 -- 标准输出
返回值: 成功 0
失败 -1;
注意事项:
1、无名管道的架设应该在fork之前进行。
关闭管道: close();
练习:父进程输入、子进程打印
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("pipe fail");
return -1;
}
char buf[100] = {0};
int ret = 0;
pid_t pid = fork();
while(1)
{
if(pid < 0)
{
perror("fork fail");
return -1;
}
else if(pid > 0)
{
close(fd[0]);
printf(">");
fflush(stdout);
ret = read(0,buf,sizeof(buf));
buf[ret] = '\0';
write(fd[1],buf,strlen(buf) + 1);
if(strncmp(buf,"quit",4) == 0)
{
wait(NULL);
close(fd[1]);
return 0;
}
}
else if(pid == 0)
{
close(fd[1]);
ret = read(fd[0],buf,sizeof(buf));
printf("date = %s\n",buf);
if(strncmp(buf,"quit",4) == 0)
{
close(fd[0]);
exit(0);
}
}
}
return 0;
}
管道的读写规则:
1.读端存在,写管道
管道空:可以写数据
管道满:会造成-->写阻塞
2.读端不存在,写管道
系统会给进程发一个信号SIGPIPE(管道破裂)
3.写端存在,读管道
管道空,读不到数据,
这时会造成读操作阻塞
4.写端不存在,读管道
如果管道中有数据,则读取这些数据!
如果没有数据,读操作不阻塞,立即返回!
2、fifo有名管道
有名管道===》fifo ==》有文件名称的管道。
文件系统中可见
框架:
(1).创建有名管道 -- 类似 文件 (管道文件)
(2).打开有名管道 -- open
(3).读写管道 -- read/write
(4).关闭管道 ==》卸载有名管道 //close
1、创建:mkfifo //创建了一个有名管道
#include <sys/types.h>
#include <sys/stat.h>
remove();
int mkfifo(const char *pathname, mode_t mode);
功能:
在指定的pathname路径+名称下创建一个权限为
mode的有名管道文件。
参数:@pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值: 成功 0
失败 -1;
2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端 //阻塞,只有双方以对应的方式打开的时候才会
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
有名管道打开:
注意,
如果一端是以只读,或者只写方式打开的。
程序会阻塞,
阻塞在打开操作。
直到另一端,以只写或只读方式打开。
A.c --- 只读
B.c --- 只写
练习:实现双向通信:
打开两个有名通道文件
// a
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
if (mkfifo("a_2_b",0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
if (mkfifo("b_2_a",0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
int fd_w = open("a_2_b",O_WRONLY);
if (fd_w< 0)
{
perror("open fail");
return -1;
}
int fd_r = open("b_2_a",O_RDONLY);
if (fd_r< 0)
{
perror("open fail");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
char buf[1024] = {0};
if (pid > 0)
{
while (1)
{
printf(">");
fflush(stdout);
fgets(buf,sizeof(buf),stdin);
write(fd_w,buf,strlen(buf)+1);
if (strncmp(buf,"quit",4) == 0)
{
close(fd_w);
close(fd_r);
}
}
}else if (pid == 0)
{
while (1)
{
printf("<");
int ret = read(fd_r,buf,sizeof(buf));
printf("ret = %d: %s\n",ret,buf);
}
}
return 0;
}
// b
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
if (mkfifo("a_2_b",0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
if (mkfifo("b_2_a",0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
int fd_r = open("a_2_b",O_RDONLY);
if (fd_r< 0)
{
perror("open fail");
return -1;
}
int fd_w = open("b_2_a",O_WRONLY);
if (fd_w< 0)
{
perror("open fail");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
char buf[1024] = {0};
if (pid > 0)
{
while (1)
{
printf(">");
fflush(stdout);
fgets(buf,sizeof(buf),stdin);
write(fd_w,buf,strlen(buf)+1);
}
}else if (pid == 0)
{
while (1)
{
printf("<");
int ret = read(fd_r,buf,sizeof(buf));
printf("ret = %d: %s\n",ret,buf);
if (strncmp(buf,"quit",4) == 0)
{
close(fd_w);
close(fd_r);
}
}
}
return 0;
}