1. 同步和异步
1.1 同步
多个任务在某一时刻,先后执行顺序可以被确定
同步操作要求一系列操作严格按照顺序执行,一个操作只有在前一个操作完成之后才能开始。在计算机编程中,这意味着当一个程序或线程发出一个请求或调用后,它会暂停执行,直到该请求完成并返回结果,这个过程通常称为阻塞。
1.2 异步
多个任务执行顺序相互独立,没有先后顺序关系
异步操作则允许一个操作开始后,调用者不必等待其完成就可以继续执行其他任务。这意味着操作的完成和结果的获取是分开的,结果通常通过回调函数、事件、消息队列或其他形式的通知机制来传达给调用者。异步机制提高了程序的并发性和响应性,因为它允许程序在等待某个慢速操作(如I/O操作)的同时处理其他任务。
1.3 总结
简而言之,同步强调操作的顺序执行和相互等待,而异步则允许操作并发执行,无需等待其他操作的完成。
2. 互斥锁、信号量、管道、信号的区别和联系
2.1 互斥锁
主要用于线程间的同步通信
确保同一时间只有一个线程可以访问共享资源。虽然在某些操作系统中,通过特定的属性设置,互斥锁可以跨进程使用,但其主要应用场景是线程同步。
2.2 信号量
既可用于线程间同步,也可用于进程间同步。也可以用于进程或线程的异步通信。
信号量可以控制对共享资源的访问数量,是一种更为灵活的同步机制,支持跨进程的同步需求。
2.3 管道
通常用于在具有亲缘关系的进程间(如父子进程)进行同步数据传输。
匿名管道是半双工的,数据只能单向流动,且主要用于进程间通信。虽然管道不直接用于线程间通信,但在某些系统中,通过进程间通信方式间接实现线程间的数据交换也是可能的。
2.4 信号
主要用于进程间的异步通信。
用于通知接收进程某个事件的发生,如终端用户请求中断进程。信号不是用于数据传输,而是简单事件的传递,且是异步的,即发送信号不需要等待接收方的响应。
2.5 总结
综上所述
1. 进程线程:信号量是唯一明确既适用于线程间又适用于进程间通信的机制。互斥锁主要针对线程同步,但特定条件下也能用于进程同步。管道主要用于进程间通信,特别是具有血缘关系的进程,而信号则是进程间的异步通信方式。
2. 同步异步:互斥锁和管道主要是同步通信机制,信号量依据使用场景既可同步也可异步,而信号则是典型的异步通信方式。
3. 原子操作
原子操作(Atomic Operation)是指在计算机科学中,那些不可中断的操作序列,这些操作要么全部完成要么完全不执行,不存在执行到一半的状态。这意味着在多线程环境或并发处理时,一个原子操作不会受到其他线程的干扰,保证了操作的完整性和一致性。
不能被CPU任务调度所打断的一次最小的操作
4. 死锁
4.1 定义
多线程任务由于加锁导致均无法向下执行的状态,称为死锁
4.2 死锁产生的四个必要条件
1.互斥条件
2.不可剥夺条件
3.请求保持
4.循环等待
4.3 死锁解决办法
1.破坏不可剥夺条件
2.使用pthread_mutex_trylock替代pthread_mutex_lock
3.加锁和解锁顺序保持一致
5. 互斥锁
5.1 引用互斥锁的原因:利用全局变量能够实现两个线程任务通信,线程任务通信操作全局变量可能会引发资源竞争,为避免资源竞争,引入互斥锁机制
5.2 定义
互斥锁可以理解为是一种资源,一旦互斥锁被加锁,另外的线程任务无法继续加锁,必须等待互斥锁被解锁,另外的任务才能加锁
5.3 临界资源(临界区)
加锁和解锁中间的代码(资源)称为临界资源(临界区),同一时刻,临界资源不能被同时执行
6. 互斥锁相关函数接口
6.1 pthread_mutex_init
1. 定义
2. 功能
初始化一个互斥锁
3. 参数
4. 返回值
5. 示例程序
6. 注意
6.2 pthread_mutex_lock
1. 定义
2. 功能
上锁
3. 参数
4. 返回值
5. 示例程序
6. 注意
6.3 pthread_mutex_unlockk
1. 定义
2. 功能
解锁
3. 参数
4. 返回值
5. 示例程序
6. 注意
6.4 pthread_mutex_destroy
1. 定义
2. 功能
销毁锁资源
3. 参数
4. 返回值
5. 示例程序
#include "head.h"
pthread_mutex_t lock;
int value1 = 0;
int value2 = 0;
int num = 0;
void *thread1(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock);
value1 = num;
value2 = num;
pthread_mutex_unlock(&lock);
num++;
}
return NULL;
}
void *thread2(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock);
if (value1 != value2)
{
printf("value1 = %d, value2 = %d\n", value1, value2);
}
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid1;
pthread_t tid2;
pthread_mutex_init(&lock, NULL);
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&lock);
return 0;
}
6. 注意
7.信号量
7.1 定义
信号量是一种资源,可以被初始化,申请和释放
7.2 PV操作
P申请、V释放
8. 信号量函数接口
8.1 sem_init
1. 定义
2. 功能
对信号量初始化
3. 参数
4. 返回值
5. 示例代码
6. 注意
8.2 sem_post
1. 定义
2. 功能
释放一个信号量(让资源数+1)
3. 参数
4. 返回值
5. 示例代码
6. 注意
8.3 sem_wait
1. 定义
2. 功能
申请信号量(让资源数-1),如果资源数为0,阻塞等待有人释放资源(资源数>0),才能申请
资源继续向下执行
3. 参数
4. 返回值
5. 示例代码
6. 注意
8.4 sem_destroy
1. 定义
2. 功能
销毁信号量
3. 参数
4. 返回值
5. 示例代码
#include "head.h"
char tmpbuff[4096] = {0};
sem_t sem_w;
sem_t sem_r;
void *readfun(void *arg)
{
while (1)
{
sem_wait(&sem_r);
printf("tmpbuff = %s\n", tmpbuff);
sem_post(&sem_w);
}
return NULL;
}
void *writefun(void *arg)
{
while (1)
{
sem_wait(&sem_w);
gets(tmpbuff);
sem_post(&sem_r);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid_w;
pthread_t tid_r;
sem_init(&sem_r, 0, 0);
sem_init(&sem_w, 0, 1);
pthread_create(&tid_r, NULL, readfun, NULL);
pthread_create(&tid_w, NULL, writefun, NULL);
pthread_join(tid_r, NULL);
pthread_join(tid_w, NULL);
sem_destroy(&sem_w);
sem_destroy(&sem_r);
return 0;
}
6. 注意
9. 管道——windows中无管道文件
9.1 无名管道:具有亲缘关系的进程间通信——父子进程、爷孙进程
9.2 有名管道:用于任意两个进程间通信
10. 无名管道(fork后子进程会继承父进程的文字描述符,实现父子进程的通信)
10.1 函数接口
pipie
1. 定义
i nt pipe(int pipefd[2]);
2. 功能
创建一个用来通信的无名管道(在内核中)
3. 参数
pipefd:存放文件描述符数组空间首地址
pipefd[0]:读管道文件描述符
pipefd[1]:写管道文件描述符
4. 返回值
成功返回0
失败返回-1
5. 示例程序
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
pid_t pid;
char tmpbuff[4096] = {0};
int pipefd[2] = {0};
int ret = 0;
ret = pipe(pipefd);
if (-1 == ret)
{
perror("pipe error!\n");
return -1;
}
pid = fork();
if (-1 == pid)
{
perror("fork error!\n");
return -1;
}
if (0 == pid)
{
strcpy(tmpbuff, "hello world");
write(pipefd[1], tmpbuff, strlen(tmpbuff)+1);
}
else if (pid > 0)
{
read(pipefd[0], tmpbuff, sizeof(tmpbuff));
printf("tmpbuff = %s\n", tmpbuff);
}
return 0;
}
10.2 无名管道中的四种特殊情况
1. 管道中至少有一个写端:
1.如果管道中有数据,直接读取
2.如果管道中没有数据,阻塞等待,直到有数据写入,再读取数据
2. 管道中没有写端
1.如果管道中有数据,直接读取
2.如果管道中没有数据,不阻塞等待,直接返回
3. 管道中至少有一个读端
1.向管道中写入数据,如果没有写满,则直接写入
2.如果写满(64k),阻塞等待数据读出,才能继续写入
4. 管道中没有读端
1.向管道中写入数据会产生管道破裂的错误
11. 有名管道函数接口
mkfifo
1,定义
int mkfifo(const char *pathname, mode_t mode);
2. 功能
创建一个有名管道
3. 参数
pathname:有名管道的路径
mode:有名管道的权限
4. 返回值
成功返回0
失败返回-1
5.示例程序
1. 头文件
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#endif
2.makefile
all:read write
read:read.c
gcc read.c -o read
write:write.c
gcc write.c -o write
.PHONY:
clean:
rm read write
3. read.c
#include "head.h"
int main(int argc, char const *argv[])
{
int fd = 0;
char tmpbuff[4096] = {0};
fd = open("/tmp/myfifo", O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
printf("管道打开成功!\n");
read(fd, tmpbuff, sizeof(tmpbuff));
printf("RECV:%s\n", tmpbuff);
close(fd);
return 0;
}
4. write.c
#include "head.h"
int main(int argc, char const *argv[])
{
int fd = 0;
char tmpbuff[4096] = {0};
mkfifo("/tmp/myfifo", 0777);
fd = open("/tmp/myfifo", O_WRONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
printf("管道打开成功!\n");
gets(tmpbuff);
write(fd, tmpbuff, strlen(tmpbuff)+1);
close(fd);
return 0;
}
6. 注意
有名管道必须读写两端同时加入后才能继续向下执行,否则以只读或只写方式打开,会发生阻塞(等待另一端的加入)
12. 信号
12.1 定义
内核层给用户层传递消息,通过发送信号实现
12.2 信号类型
kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
SIGINT:中断信号(可以在终端按ctrl + c输入)
SIGQUIT:退出信号(可以在终端按ctrl + \输入)
SIGKILL:杀死信号
SIGSEGV:段错误信号
SIGPIPE:管道破裂信号
SIGALRM:定时信号
SIGCHLD:当前进程有子进程结束(子进程结束,操作系统会给父进程发送SIGCHLD)
SIGCONT:继续执行信号
SIGSTOP:暂停信号
SIGIO:异步IO信号
SIGTSTP:挂起信号(可以在终端按ctrl + z输入)
12.3 信号处理方式
1.缺省(按照默认处理方式)
2.忽略(不响应信号)
3.捕捉(按自定义方式处理信号)
注意:
9号:SIGKILL
19号:SIGSTOP
不能被忽略和捕捉
13. 信号函数接口
13.1 signal
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
6. 注意
13.2 pause
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
6. 注意
13.3 raise
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
6. 注意
13.4 alarm
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
6. 注意
13.5 kill
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
6. 注意
13.1 signal
1. 定义
2. 功能
3. 参数
4. 返回值
5. 示例程序
#include "head.h"
pid_t pid;
void handler_child(int signo)
{
if (SIGINT == signo)
{
printf("爸,我回来了!\n");
kill(getppid(), SIGUSR1);
}
else if (SIGUSR2 == signo)
{
printf("哦\n");
}
return;
}
void handler_father(int signo)
{
if (SIGUSR1 == signo)
{
printf("快去写作业!\n");
}
else if (SIGQUIT == signo)
{
printf("儿子,我回来了\n");
kill(pid, SIGUSR2);
}
return;
}
int main(int argc, char const *argv[])
{
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
signal(SIGINT, handler_child);
signal(SIGQUIT, SIG_IGN);
signal(SIGUSR2, handler_child);
}
else if (pid > 0)
{
signal(SIGINT, SIG_IGN);
signal(SIGUSR1, handler_father);
signal(SIGQUIT, handler_father);
}
while (1)
{
}
return 0;
}
6. 注意