进程同步是指对多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有
效地共享资源和相互合作,从而使程序的执行具有可在现行。
首先,程序在调用fork()机那里了一个子进程后,马上调用wait(),使父进程在子进程调
用之前一直处于睡眠状态,这样就使子进程先运行,子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行,实现进程同步。
进程同步是指对多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有
效地共享资源和相互合作,从而使程序的执行具有可在现行。
首先,程序在调用fork()机那里了一个子进程后,马上调用wait(),使父进程在子进程调
用之前一直处于睡眠状态,这样就使子进程先运行,子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行,实现进程同步。
转载于:https://www.cnblogs.com/kazama/p/10734962.html
前文:
单线程——多线程的开启——线程锁——线程同步工具——手写连接池——连接池工具类。
一、线程
1.线程的概念
2.线程与进程的关系
3.定义:
区别:如上!!!
4.wait()和sleep()
5.线程的状态及其他API
二、线程锁
线程并发同步时,引入了锁机制。
1. 普通锁机制:synchronized 修饰代码块与volatile 修饰成员变量
2.Lock
!!共同点:都是从外面创建锁类、再把锁传到线程里对变量对象赋值。
(1)重入锁
(2)读写分离锁
区别:
二、线程同步工具类
!!共同点:都是从外面创建工具类、再把工具类的参数传到线程里面执行。
1.CountDowmLatch闭锁:等待所有线程执行完
2.CyclicBarrier栅栏:等待所有线程达到后开启
3.Exchanger交换机:交流数据
4.信号量
(1)概念
(2)应用场景一
(3)应用场景二
三、线程池
这里分享一个手写代码实现线程池的视频,需要代码的朋友可以后台私信【架构】获取
线程池视频链接:手写代码现实线程池
1.为什么使用线程池
2.线程池的核心队列
阻塞式队列:只用于线程对象,主要用于引出线程池
3.手动创建线程池
4.Executors工具创建线程
核心线程:0(临时线程)、1(队列)、N(队列)
定时线程:
四、彩蛋图
信号量方法的基本原则:两个或多个进程可以用信号的方法进行协作;进程可以在任何地方停下来以等待收到特定的信号;信号的实现是用一种称为信号量(Semaphore)的特殊变量。
信号量S就是一个特殊变量,包含一个整数值。
在S上可以执行两个原子操作:wait(S)用来接受信号,也称为P操作;signal(S)用来发送信号,也称为V操作。信号量分类:计数信号量(Counting Semaphore)、二元信号量(Binary Semaphore)、结构信号量(Structure Semaphore),
结构型信号量
结构型信号量数据结构
typedef struct { int value; struct process *L; // L指向一个进程队列 }
wait操作
void wait(semaphore *S) { s.value--; if (s.value < 0) { add this process into S.L; block(); } }
signal操作
void signal(semaphore *S) { s.value++; if (s.value <= 0) { remove a process P from S.L; wakeup(P); } }
如何用信号量实现互斥?
用信号量实现互斥的方法:
(S.value初始值为1)
wait(S);
临界区
signal(S);
例如:有三个进程P、Q、R欲进入临界区,S.value初始值为1。P进入时,先执行wait(S),此时S.value=0,不满足if条件,则wait结束,P进入临界区。若此时Q想要进入临界区,也需执行wait,此时S.value=-1,满足<0条件,则将进程加入S,L,block阻塞进程,使其等待。同理若R想进入临界区,则有S.value=-2,R进程也阻塞。
此时若P从临界区出来,执行signal,此时S.value=-1,满足if条件,从等待进程中选择一个进入临界区,假设按照FCFS算法,使Q进入。同理Q出临界区后,S.value=0,R进入,R出临界区后,S.value=1,回归初始值。如何用信号量实现同步?
用信号量实现同步的方法:
S.value初始值为0
在先执行的进程段后面加signal(S)
后执行的进程段前面加wait(S)
例子:假设有P1和P2两个进程,要求P1进程的A段必须在P2进程的B段之前执行。
(1) 若此时P1先执行,执行完A段后,执行signal(S),此时S.value=1,不满足signal中的if条件,不进行任何操作,结束signal。再执行P2的B段之前,执行wait(S),此时S.value=0,不满足wait中的if条件,不进行任何操作,结束wait,执行B。
(2) 若此时P2先执行,执行B段之前先执行wait(S),此时S.value=-1,满足wait中的if条件,B段进入S.L队列,阻塞。执行P1中的A段,结束后执行signal(S),此时S.value=0,满足signal中的if条件,将B移出等待队列,唤醒B进程。信号量取值特点
一个信号量的值可以为负数,其绝对值表示有多少个进程等待这个信号量。
一个信号量的值可以为正数,表示有一个或多个被积累下来的唤醒操作。使用信号量时需要避免的错误
死锁:两个或多个进程无限地等待一个事件,而这个事件只能由这些等待进程之一来产生。
饥饿:无限期阻塞,即进程在信号内无限等待的情况。参考文献
北京工业大学网课《操作系统原理》金雪云
拥有梦想是一种智力,实现梦想是一种能力。
概述
并发程序是应用开发中非常重要的一部分内容,如何实现程序的并发?包括多进程编程、进程间通信机制、多线程编程、线程间同步和异步机制等等。本次介绍多进程编程:
系统调用fork允许一个进程(父进程)创建一个新进程(子进程)。通过fork,子进程几乎是父进程的复制版本,子进程获得父进程的栈、数据段、堆和执行文本段的拷贝。通常,调用fork产生子进程后,子进程随便会调用execve函数簇执行新的任务,随后执行exit相关函数退出。而父进程则通常会调用wait函数等待子进程终止。此外,我会通过程序的方向来介绍,如何让一个子进程脱离它原有的终端成为一个守护进程运行。
pid_t fork(void);
- 创建新的进程,失败时返回-1
- 成功时父进程返回子进程的进程号,子进程返回0
- 通过fork的返回值区分父进程和子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
if ((pid = fork()) < 0) {
perror(“fork”);
return -1;
}
else if (pid == 0) {//子进程执行
printf(“child process : my pid is %d\n”, getpid());
}
else {//父进程执行
printf(“parent process : my pid is %d\n”, getpid());
}
return 0;
}
此时,子进程fork()函数之后,而不是程序的开始处进行。由于子进程继承了父进程的所有内容,所以两个进程的执行没有差分。
void exit(int status);
void _exit(int status);
- 结束当前的进程并将status返回
exit与_exit的区别是,exit结束进程时会刷新(流)缓冲区。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("this process will exit");//
exit(0);
printf("never be displayed");
}
由于shell终端是行缓冲输出,printf输出的字符串又没有换行,所以 "this process will exit" 会驻留在缓冲区。exit()操作会刷新缓冲区,终端显示 "this process will exit"。
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
- 成功时执行指定的程序;失败时返回EOF
- path 执行的程序名称,包含路径
- arg… 传递给执行的程序的参数列表
- file 执行的程序的名称,在PATH中查找
进程调用exec函数族执行某个程序,进程当前内容被指定的程序替换。从而实现让父子进程执行不同的程序。
#include <stdio.h>
#include <unistd.h>
int main()
{
if (execl(“/bin/ls”, “ls”, “-a”, “-l”, “/etc”, NULL) < 0)
{
perror(“execl”);
}
return 0;
}
问:如何让父子进程执行不同程序?
答:fork()创建一个子进程,然后exec()让进程执行其他程序,这样就达到了父子进程执行不同程序目的。
如果子进程结束了但是没有被回收的话,此时进程进入死亡态,叫做僵尸进程。僵尸进程的进程控制块依然存在(PCB),会占用资源,务必对其进行回收。通过父进程对其回收。
#include <unistd.h>
pid_t wait(int *status);
- 成功时返回回收的子进程的进程号;失败时返回EOF
- 若子进程没有结束,父进程一直阻塞
- 若有多个子进程,哪个先结束就先回收
- status 指定保存子进程返回值和结束方式的地址
- status为NULL表示直接释放子进程PCB,不接收返回值
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int status;
pid_t pid;
if ((pid = fork()) < 0) {
perror(“fork”);
exit(-1);
}
else if (pid == 0) {
sleep(1);
exit(2);
}
else {
wait(&status);
printf(“%x\n”, status);
}
return 0;
}
wait()函数被父进程执行,父进程便进入阻塞态(一直等待),直到子进程结束。status保存子进程的返回值和结束方式地址。
守护进程通常在系统启动时运行,系统关闭时结束。Linux很多服务程序以守护进程形式运行。其特点
Linux以会话(session)、进程组的方式管理进程。例如,我在打开shell(一个会话建立),运行一个脚本程序,然后关闭sell终端(会话关闭),程序也随之结束。而守护进程是不会随着终端关闭而结束的!!!
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main() {
pid_t pid;
FILE *fp;
time_t t;
int i;
if ((pid = fork()) < 0)
{
perror(“fork”);
exit(-1);
}
else if (pid > 0)
{
exit(0);
}
setsid();
umask(0);
chdir(“/tmp”);
for (i=0; i< getdtablesize(); i++)
{
close(i);
}
if ((fp = fopen(“time.log”, “a”)) == NULL)
{
perror(“fopen”);
exit(-1);
}
while ( 1 )
{
time(&t);
fprintf(fp, “%s”, ctime(&t));
fflush(fp);
sleep(1);
}
return 0;
}
if ((pid = fork()) < 0)
{
perror(¡°fork¡±);
exit(-1);
}
else if (pid > 0)
{
exit(0);
}
setsid();
umask(0);
chdir()
for (i=0; i< getdtablesize(); i++)
{
close(i);
}