2.1 子进程的概念
- 子进程返回0,父进程返回子进程的进程id;
- 子进程是父进程的副本;
- 子进程获得了父进程的数据空间、堆、和栈的副本、不是共享。
2.2 多进程程序的退出处理
* 程序名:demo10.cpp,此程序演示采用开发框架的CTcpServer类实现socket通讯多进程的服务端。
* 1)在多进程的服务程序中,如果杀掉一个子进程,和这个子进程通讯的客户端会断开,但是,不
* 会影响其它的子进程和客户端,也不会影响父进程。
* 2)如果杀掉父进程,不会影响正在通讯中的子进程,但是,新的客户端无法建立连接。
* 3)如果用killall+程序名,可以杀掉父进程和全部的子进程。
*
* 多进程网络服务端程序退出的三种情况:
* 1)如果是子进程收到退出信号,该子进程断开与客户端连接的socket,然后退出。
* 2)如果是父进程收到退出信号,父进程先关闭监听的socket,然后向全部的子进程发出退出信号。
* 3)如果父子进程都收到退出信号,本质上与第2种情况相同。
2.3 异步通信的三种实现方式
1)多进程:用不同的进程发送报文和接受报文;(一个进程用于发送报文,一个进程用于接收报文)
2)多线程:用不同的线程发送报文和接受报文;
3) I/O复用:select、poll、epoll函数。
- TCP(socket网络编程)
- 在网络编程中,通过会忽略SIGPIPE信号,防止程序异常退出。
原因:
TCP 是全双工的信道,可以看作两条单工信道,TCP 连接两端的两个端点各负责一条。当对端调用 close 时,虽然本意是关闭整个两条信道,但本端只是收到 FIN 包,按照 TCP 协议的语义,表示对端只是关闭了其所负责的那一条单工信道,仍然可以继续接收数据。也就是说,因为 TCP 协议的限制,一个端点无法获知对端的 socket 是调用了 close 还是 shutdown。
对一个已经收到 FIN 包的 socket 调用 read 方法,如果接收缓冲已空,则返回 0,这就是常说的表示连接关闭。但第一次对其调用 write 方法时,如果发送缓冲没问题,会返回正确写入(发送)。但发送的报文会导致对端发送 RST 报文,因为对端的 socket 已经调用了 close,完全关闭,既不发送,也不接收数据。所以,第二次调用 write 方法(假设在收到 RST 之后),会生成 SIGPIPE 信号,导致进程退出。
做法:
Signal(SIGPIPE,SIG_IGN);
- 如果数据量小的表,采用全量抽取;如果数据量大的表,采用增量量抽取。
- Linux线程
12.1exit和return的区别
exit用于结束正在运行的整个程序,它将参数返回给OS,把控制权交给操作系统;而return 是退出当前函数,返回函数值,把控制权交给调用函数。
12.2进程和线程的区别
进程:拥有独立的PCB,有独立的地址空间;
线程:light weight process轻量级的进程,本质仍然是进程;
线程:拥有PCB,没有独立的地址空间;
进程:最小的分配资源单位,多个线程共享进程的资源;
线程:CPU最小的执行和调度单位;
是否共享地址空间,独居(进程);合租(线程)。
在多进程中,子进程core dump掉不影响其他进程;
在多线程中,子线程core dump掉,整个进程玩完。
12.3线程的优缺点
可以在一个进程内实现并发;
开销少,创建线程比进程快;
数据通信、数据共享方便,同时也增加了开发难度。
12.4 线程和信号
1.在多线程中,外部向进程发送信号不会中断系统调用;在多进程中,外部向进程发送信号会中断系统调用;
2.在多线程中,信号的处理是所有线程共享的。
3.进程中的信号可以送达单个线程,会中断系统调用。
4.如果某线程因为信号而终止,整个进程将终止。
12.5 线程安全
造成线程安全的原因:
// wget http://192.168.184.101:8080/?username=ty&passwd=typwd&intername=getzhobtmind3&obtid=59287&begintime=20211024114020&endtime=20230510155322
// wget "http://192.168.184.101:8080/?username=ty&passwd=typwd&intername=getzhobtmind3&obtid=59287&begintime=20211024114020&endtime=20230509182520" -O /dev/null -a /dev/null &
- 多个线程访问共享资源(全局和静态)的时候会冲突。
- 是否调用了不可重入的函数
-
三个概念:原子性、 可见性、 顺序性。
原子性:
一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部不执行(都不生效)。
CPU执行指令:读取指令、读取内存、执行指令、写回内存。
i++ 1)从内存中读取i的值;2)把i+1; 3)把结果写回内存。
可见性:
当多个线程并发访问共享变量时,一个线程对共享变量的修改,其他线程能够立即看到。
CPU有高速缓存。每个线程读取内存变量时,会将该变量从内存加载到CPU的缓存中,修改该变量后,CPU会立即更新缓存,但不一定会立即将它写回内存。此时其他线程访问该变量,从内存中读到的是旧数据,而非第一个线程更新后的数据。
顺序性:
程序执行的顺序按照代码的先后顺序执行。
CPU为了提高程序整体的执行效率,可能会对代码进行优化,按照更高效的顺序执行代码。
CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果喝代码顺序执行时的结果一致。
解决办法:
volatile关键字:
保证了变量的内存可见性; 禁止代码重排序;
原子操作(原子类型):
本质是总线(CPU和内存直接的线)锁;
三条汇编指令:xadd、cmpxchg、xchg;
硬件级别的锁;
C++11原子类型;
Std::atomic模板类封装了原子操作,支持布尔、整数和字符(所以需要锁,来对一段代码或者对象进行操作)
Demo:
__sync_fetch_and_add(&var,1); // 等价于i++
线程同步(锁):
彻底解决了原子性、可见性、顺序性。
分为了:互斥锁、自旋锁、读写锁、条件变量、信号量、生产消费者模型。
互斥锁:
加锁和解锁,确保同一时间只有一个线程访问共享资源;
访问共享资源之前加锁,访问玩抽释放锁;
如果某线程持有锁,其他的线程形成等待队列。
自旋锁:
与互斥锁的区别,不等着,一直循环等待解锁,消耗CPU资源。
读写锁:
读写锁允许更高的并发性;
三种状态:读模式加锁、写模式加锁和不加锁。
特点: 只要没有线程持有写锁,任意线程都可以成功申请读锁;
只有在不加锁状态时,才能成功申请写锁。
条件变量:
与互斥锁一起使用;
实现生产消费者模型;
实现通知的功能。
信号量:
一个整数计数器,其数值用于表示空闲临界资源的数量;
申请资源时,信号量减少,表示可用资源数减少;
释放资源时,信号量增加,表示可用资源数增加。
竞争机制的区别: 自旋锁、条件变量、信号量(等待队列)
互斥锁(重新竞争和等待队列)、读写锁(特别)
生产消费者模型:
互斥锁+条件变量实现生产消费者模型;(支持多线程和多进程)
-
信号量实现实现生产消费者模型。 (仅支持多进程)
-
Pthread_cond_wait(&cond,&mutex) // cond 和 mutex条件都要满足,即收到信号和持有锁
- 把互斥锁解锁;
- 阻塞,等待条件(被唤醒);
- 条件被触发+给互斥锁加锁。(是原子操作)