linux环境编程之守护进程
标签: # C++
进程&子进程
什么是进程
计算机实际上可以做的事情实质上非常简单,比如计算两个数的和,再比如在内存中寻找到某个地址等等。这些最基础的计算机动作被称为指令 (instruction)。所谓的程序(program),就是这样一系列指令的所构成的集合。通过程序,我们可以让计算机完成复杂的操作。程序大多数时候被存储为可执行的文件。这样一个可执行文件就像是一个菜谱,计算机可以按照菜谱作出可口的饭菜。
那么,程序和进程(process)的区别又是什么呢?
进程是程序的一个具体实现。只有食谱没什么用,我们总要按照食谱的指点真正一步步实行,才能做出菜肴。进程是执行程序的过程,类似于按照食谱,真正去做菜的过程。同一个程序可以执行多次,每次都可以在内存中开辟独立的空间来装载,从而产生多个进程。不同的进程还可以拥有各自独立的IO接口。
操作系统的一个重要功能就是为进程提供方便,比如说为进程分配内存空间,管理进程的相关信息等等,就好像是为我们准备好了一个精美的厨房。
首先,我们可以使用 p s 命 令 来 查 询 正 在 运 行 的 进 程 , 比 如 ‘ ps命令来查询正在运行的进程,比如` ps命令来查询正在运行的进程,比如‘ps -eo pid,comm,cmd`,下图为执行结果:
(-e表示列出全部进程,-o pid,comm,cmd表示我们需要PID,COMMAND,CMD信息)
$ ps -eo pid,comm,cmd
PID COMMAND CMD
1 systemd /usr/lib/systemd/systemd --switched-root --system --deserialize
2 kthreadd [kthreadd]
4 kworker/0:0H [kworker/0:0H]
6 ksoftirqd/0 [ksoftirqd/0]
7 migration/0 [migration/0]
每一行代表了一个进程。每一行又分为三列。第一列PID(process IDentity)是一个整数,每一个进程都有一个唯一的PID来代表自己的身份。第二列COMMAND是这个进程的简称。第三列CMD是进程所对应的程序以及运行时所带的参数。
(第三列有一些由中括号[]括起来的。它们是内核的一部分功能,被打扮成进程的样子以方便操作系统管理。我们不必考虑它们。)
如何创建一个进程
实际上,当计算机开机的时候,内核(kernel)只建立了一个systemd进程。Linux内核并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。fork是一个系统调用。进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间 (address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。
老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是systemd进程。所以说,所有的进程也构成一个以systemd为根的树状结构。
如下,我们查询当前shell下的进程:
$ ps -o pid,ppid,cmd
PID PPID CMD
9574 9566 bash
10168 9574 ps -o pid,ppid,cmd
还可以用$pstree命令来显示整个进程树:
$ pstree
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager───2*[{NetworkManager}]
├─VGAuthService
├─2*[abrt-watch-log]
├─abrtd
├─accounts-daemon───2*[{accounts-daemon}]
├─alsactl
├─at-spi-bus-laun─┬─dbus-daemon───{dbus-daemon}
│ └─3*[{at-spi-bus-laun}]
├─at-spi2-registr───2*[{at-spi2-registr}]
├─atd
├─auditd─┬─audispd─┬─sedispatch
│ │ └─{audispd}
│ └─{auditd}
├─avahi-daemon───avahi-daemon
fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。实际上,子进程总可以查询自己的PPID来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。
通常在调用fork函数之后,程序会设计一个if选择结构。
- 当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的: 为某一程序创建进程);
- 而当PID为一个正整数时,说明为父进程,则执行另外一些指令。
由此,就可以在子进程建立之后,让它执行与父进程不同的功能。
子进程的终结(termination)
当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在内核里留下自己的退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为>0的整数)。在这个信息里,会解释该进程为什么退出。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。这个wait函数能从内核中取出子进程的退出信息,并清空该信息在内核中所占据的空间。但是,如果父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。
当然,一个糟糕的程序也完全可能造成子进程的退出信息滞留在内核中的状况(父进程不对子进程调用wait函数),这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。
尽管在UNIX中,进程与线程是有联系但不同的两个东西,但在Linux中,线程只是一种特殊的进程。多个线程之间可以共享内存空间和IO接口。所以,进程是Linux程序的唯一的实现方式。
总结:
在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵死进程,无法正常结束,此时即使是root身份kill -9也不能杀死僵死进程。补救办法是杀死僵尸进程的父进程(僵死进程的父进程必然存在),僵死进程成为”孤儿进程”,过继给1号进程init,init始终会负责清理僵死进程。
进程关系
进程组
理论
每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个进程组领导进程 (process group leader),领导进程的PID (PID见Linux进程基础)成为进程组的ID (process group ID, PGID),以识别进程组。
一般情况下,一个进程组是由一个进程 fork 出来的,之后,它的子进程再去 fork ,最后,得到了一个进程组。当然,单个的进程也是一个进程组。进程组有进程组 id ,它通常是第一个进程的 pid ,也就是组长进程的 pid (以识别进程组)。
$ps -o pid,pgid,ppid,comm | cat
PID PGID PPID COMMAND
17763 17763 17751 bash
18534 18534 17763 ps
18535 18534 17763 cat
PID为进程自身的ID,PGID为进程所在的进程组的ID, PPID为进程的父进程ID。从上面的结果,我们可以推测出如下关系:

图中箭头表示父进程通过fork和exec机制产生子进程。ps和cat都是bash的子进程。进程组的领导进程的PID成为进程组ID。领导进程可以先终结。此时进程组依然存在,并持有相同的PGID,直到进程组中最后一个进程终结。
创建进程组的目的是用于简化向组内所有进程发送信号的操作,即如果一个信号是发给一个进程组,则这个组内的所有进程都会受到该信号【方便管理】。
API
进程组ID和进程ID相似:它们是正 的整型,并以pid_t类型存储。函数getpgrp返回调用进程的进程组ID。
#include <unistd.h>
/***********************
*功能:获取进程组ID
*返回值:成功返回进程组ID,失败返回-1并设置errno
* *********************/
pid_t getpgrp(void);
在BSD后代系统的早期版本,getpgrp函数接受一个pid参数并返回这个进程的进程组。SUS定义getpgid函数作为一个XSI扩展来效仿这种行为。
/**************************
* 功能:获取进程GID
* 参数:pid ---- 进程ID
* 如果pid为0,返回调用进程的进程组ID。因而getpgid(0)等价于getpgrp();
* 返回值:成功返回进程组ID,失败返回-1并设置errno
* ***********************/
pid_t getpgid(pid_t pid);
通过调用setpgid,可以让一个进程加入一个存在的进程组或创建一个新的进程组
/*****************************
* 功能: 设置进程组ID
* 参数: pid进程ID
* pgid进程组ID
* 如果pid的实参为0,setpgid(0, 5)等价于setpgid(getpid(), 5)
* 如果pgid为0,setpgid( 5, 0)等价于setpgid( 5, getpid())
* 返回值:成功返回0,错误返回-1
* 注意: 一个进程只能为它自己或它的子进程设置进程组ID
* 在它的子进程调用了exec后,它就不再能改变该子进程的进程组ID
****************************/
int setpgid(pid_t pid, pid_t pgid);
实践
kill 进程组
一个进程所创建的子进程,都会被包含到一个进程组中。
所以,我们可以用进程组杀死某个进程及其fork出的所有子进程。
$ ps -o pgid 19843
PGID
977
$ kill -- -977
$ kill -SIGTERM -- -977
posix中的进程组
显示子进程与父进程的进程组id
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
printf("start fork......\n");
if((pid=fork())<0){
perror("fork");
exit(1);
}else if(pid==0){
printf("The child process PID is %d.\n",getpid());
printf("The Group ID is %d.\n",getpgrp());
printf("The Group ID is %d.\n",getpgid(0));
printf("The Group ID is %d.\n",getpgid(getpid()));
}else{
sleep(3);
printf("The parent process PID is %d.\n",getpid());
printf("The Group ID is %d.\n",getpgrp());
}
exit(0);
}

下面子进程使用setpgid()创建了一个新的进程组,并且进程组的组长就是自己。从而脱离了父进程的进程组
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
printf("start fork......\n");
if((pid=fork())<0){
perror("fork");
exit(1);
}else if(pid==0){
printf("Child:before setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp());
setpgid(getpid(),getpid());
printf("Child:after setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp());
}else{
wait(NULL);
printf("child exit\n");
printf("Father:My pid=%d,My pgid=%d\n",getpid(),getpgid(getpid()));
}
exit(0);
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
printf("start fork......\n");
if((pid=fork())<0){
perror("fork");
exit(1);
}else if(pid==0){
printf("Child:before setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp());
sleep(2);
printf("Child:after setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp());
}else{
sleep(1);
setpgid(pid,pid);
wait(NULL);
printf("child exit\n");
printf("Father:My pid=%d,My pgid=%d\n",getpid(),getpgid(getpid()));
}
exit(0);
}

会话 (session)
理论
多个进程组构成一个「会话」,建立会话的进程是会话的领导进程,该进程 ID 为会话的 SID。会话中的每个进程组称为一个「作业」。会话可以有一个进程组称为会话的「前台作业」,其它进程组为「后台作业」
一个会话可以有一个控制终端,当控制终端有输入和输出时都会传递给前台进程组,比如Ctrl + Z。会话的意义在于能将多个作业通过一个终端控制,一个前台操作,其它后台运行。
进程组(工作)的概念较为简单易懂。而会话主要是针对一个终端建立的。当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。这样,我们就为进程增加了管理和运行的层次。在没有图形化界面的时代,会话允许用户通过shell进行多层次的进程发起和管理。比如说,我可以通过shell发起多个后台工作,而此时标准输入输出并不被占据,我依然可以继续其它的工作。如今,图形化界面可以帮助我们解决这一需求,但工作组和会话机制依然在Linux的许多地方应用。
API
1、一个进程通过调用setsid函数来建立一个新的会话。
#include <unistd.h>
pid_t setsid(void);
//返回值:成功返回进程组ID;出错返回-1
调用此函数分为以下两种情况
①如果调用此函数的进程不是一个进程组组长,则此函数创建一个新会话:
-
此进程变成该新会话的会话首进程(session leader,会话首进程是创建该会话的进程)。此时,该进程是该会话中的唯一进程
-
此进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID
-
此进程没有控制终端。如果在调用setsid之前此进程有一个控制终端,那么这种联系也被解除
②如果调用此函数的进程是一个进程组组长,则此函数出错返回 -
为了保证不处于这种情况,通常先调用fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组ID, 而其进程ID则是新分配的,两者不可能相等,所以这就保证了子进程不是一个进程组的组长
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程…
2、获取会话首进程的进程组ID(getsid函数)
#include <unistd.h>
//返回值:成功返回会话首进程的进程组ID;失败返回-1
pid_t getsid(pid_t pid);
实践
会话ID
例如:下面的一个会话中,有3个进程组

通常是由shell的管道线将几个进程编成一组的,则上图的安排可能是由下列形式的shell命令形成的

shell中的前台后台进程
一个命令可以通过在末尾加上&方式让它在后台运行:
$ ping localhost > log &
[1] 4345 # 括号中的1表示工作号,而10141为PGID
$ ps -o pid,pgid,ppid,sid,tty,comm
PID PGID PPID SID TT COMMAND
3772 3772 3764 3772 pts/0 bash
4345 4345 3772 3772 pts/0 ping
4355 4355 3772 3772 pts/0 ps
信号可以通过kill
$kill -SIGTERM -4345
或者
$kill -SIGTERM %1
的方式来发送给工作组。上面的两个命令,一个是发送给PGID(通过在PGID前面加-来表示是一个PGID而不是PID),一个是发送给工作1(%1),两者等价。
一个工作可以通过$fg从后台工作变为前台工作:
$cat > log &
$fg %1
当我们运行第一个命令后,由于工作在后台,我们无法对命令进行输入,直到我们将工作带入前台,才能向cat命令输入。在输入完成后,按下CTRL+D来通知shell输入结束。

守护进程
理论
「守护进程」是 Linux 的一种长期运行的后台服务进程,也有人称它为「精灵进程」。我们常见的 httpd、named、sshd 等服务都是以守护进程 Daemon 方式运行的,通常服务名称以字母d结尾,也就是 Daemon 第一个字母。与普通进程相比它大概有如下特点:
- 无需控制终端(不需要与用户交互)
- 在后台运行
- 生命周期比较长,一般是随系统启动和关闭
守护进程是个特殊的孤儿进程,这种进程脱离终端。为什么要脱离终端呢?
之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
如何查看守护进程
- a 表示不仅列当前用户的进程,也列出所有其他用户的进程
- x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程
- j 表示列出与作业控制相关的信息

从上图可以看出守护进行的一些特点: - 守护进程基本上都是以超级用户启动( UID 为 0 )
- 没有控制终端( TTY 为 ?)
- 终端进程组 ID 为 -1 ( TPGID 表示终端进程组 ID)
一般情况下,守护进程可以通过以下方式启动:
- 在系统启动时由启动脚本启动,这些启动脚本通常放在 /etc/rc.d 目录下;
- 利用 inetd 超级服务器启动,如 telnet 等;
- 由 cron 定时启动以及在终端用 nohup 启动的进程也是守护进程。
如何编写守护进程?
(1) 屏蔽一些控制终端操作的信号
这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);
(2) 生成一个进程,使之脱离控制终端、登录会话和进程组
- 创建子进程,父进程退出。
- 程序中的父进程一般是一个进程组的组长,fork()产生子进程(将来会生成守护进程)
- 由于在调用fork()函数时,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出了,但原先的会话期、进程组和控制终端等并没有改变,因此,还不是真正意义上的独立;
- 由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程收养。原先的子进程就会变成init进程的子进程。
- 为什么要父进程退出:进程组组长无法调用
setsid
- 调用setsid(),用于生成一个新的会话 注意如果当前进程是会话组长时,调用失败。第一点已经可以保证进程不是会话组长了,所以setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离;
// 2.1)创建子进程,父进程退出
if( pid=fork() ){ // 父进程
exit(0); //结束父进程,子进程继续
}else if(pid< 0){ // 出错
perror("fork");
exit(EXIT_FAILURE);
}
// 2.2)脱离控制终端、登录会话和进程组
setsid();
关于 setsid
• 首先内核会创建一个新的会话,并让该进程成为该会话的leader进程,
• 同时伴随该session的建立,一个新的进程组也会被创建,同时该进程成为该进程组的组长。
• 该进程此时还没有和任何控制终端关联。若需要则要另外调用tcsetpgrp
(3) 禁止进程重新打开控制终端
- 进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。(只有会话组长才能打开控制终端)
// 3)禁止进程重新打开控制终端
if( pid=fork() ){ // 父进程
exit(0); // 结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
}else if(pid< 0){ // 出错
perror("fork");
exit(EXIT_FAILURE);
}
(4) 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误;
// NOFILE 为 <sys/param.h> 的宏定义
// NOFILE 为文件描述符最大个数,不同系统有不同限制
for(i=0; i< NOFILE; ++i){// 关闭打开的文件描述符
close(i);
}
(5) 改变当前工作目录
使用fork()创建的子进程继承了父进程的当前工作目录。
由于在进程运行过程中,当前目录所在的文件系统(如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(如系统由于某种原因要进入单用户模式)。
因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir()。
chdir("/tmp");
(6) 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
umask(0);
(7) 处理 SIGCHLD 信号
但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源(关于僵尸进程的更多详情, 请看《特殊进程之僵尸进程》)。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。关于信号的更详细用法, 请看《信号中断处理》。
signal(SIGCHLD, SIG_IGN);
总结:
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/syslog.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int init_daemon(void)
{
int pid;
int i;
// 1)屏蔽一些控制终端操作的信号
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);
// 2)在后台运行
if( pid=fork() ){ // 父进程
exit(0); //结束父进程,子进程继续
}else if(pid< 0){ // 出错
perror("fork");
exit(EXIT_FAILURE);
}
// 3)脱离控制终端、登录会话和进程组
setsid();
// 4)禁止进程重新打开控制终端
if( pid=fork() ){ // 父进程
exit(0); // 结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
}else if(pid< 0){ // 出错
perror("fork");
exit(EXIT_FAILURE);
}
// 5)关闭打开的文件描述符
// NOFILE 为 <sys/param.h> 的宏定义
// NOFILE 为文件描述符最大个数,不同系统有不同限制
for(i=0; i< NOFILE; ++i){
close(i);
}
// 6)改变当前工作目录
chdir("/tmp");
// 7)重设文件创建掩模
umask(0);
// 8)处理 SIGCHLD 信号
signal(SIGCHLD,SIG_IGN);
return 0;
}
int main(int argc, char *argv[])
{
init_daemon();
while(1);
return 0;
}

参考
Linux系统编程——特殊进程之守护进程
进程管理(进程组与会话:getpgrp、getpgid、setsid、getsid)
智能推荐
Unix环境编程之 进程环境
(1)当执行程序时,main函数是的如何调用的 main函数原型是 内核执行c程序时(使用exec函数)调用main之前先调用一个特殊例程。例程从内核取得命令行参数 和环境变量值。 (2)各种不同的进程终止方式 这三个函数都可以用来正常终止一个程序:_exit 和_Exit立即进入内核,exit则执行一些清理操作(调用执行各终止处理程序,关闭所有标准I/O流) 三个函数都带一个整型参数称为终止状态...
网络编程
主要内容 软件架构CS/BS 网络通信三要素 TCP通信 Socket套接字 ServerSocket 教学目标 能够辨别UDP和TCP协议特点 能够说出TCP协议下两个常用类名称 能够编写TCP协议下字符串数据传输程序 能够理解TCP协议下文件上传案例 能够理解TCP协议下BS案例 第一章 网络编程概述 1.1 软件结构 C/S结构 :全称为Client/Server结构,是指客户端和服务器结构...
JavaScript实现软件光栅化渲染器01-如何画点
项目代码:https://github.com/foupwang/JavaScript3DRenderer 开发环境:VSCode+Chrome浏览器 参考:《Windows游戏编程大师技巧》(第2版) /《3D游戏编程大师技巧》(André LaMothe) QQ交流群:1148938167(欢迎加入探讨图形渲染技术) 整个项目跟平台相关的,只需要一个类似DrawPixel的绘制像...
android基础之(动态权限申请)
https://blog.csdn.net/yushuangping/article/details/83758957 背景: 1: android权限的申请,可以在清单文件声明和注册,也可以通过 java 文件动态的注册 2: 在Android6.0之前只需在AndroidManifest.xml文件写明权限即可。但是在A...
猜你喜欢
脚本尝试mac电脑自动切换公司wifi
公司内部网络,每次需要登录。需要几步,选择WIFI 弹出登录页,输入用户密码才可登录成功。 因此,有一个想法利用脚本进行wifi自动登录。 提供一些思路,做出来体验不好,凑合能用,路过大神予以改进。 实现情况如下 1.需要获取登录url。就是弹出界面需要登录的url 利用抓包软件,可以做此事。mac中软件charles 主要是抓取登录时候的curl。如此可以通过curl命令直接进行登录。 2.自动...
反素数入门 zoj 2562 and codefore
学习资料来源传送们 反素数的定义:对于任何正整数,其约数个数记为,例如,如果某个正整数满足:对任意的正整 数,都有,那么称为反素数 定义理解:素数的约数只有两个 1 和 本身,而反素数的约数是尽可能多,比这个数小到正数的约数都要多 在ACM竞...
Scala语言基础(三)练习
(1)打印九九乘法表 简写 (二)worldCount实现 1.推演过程 2.综合起来 3.两个简化点 a. 其中,array.map(x => x.split(" “)).flatten效果和array.flatMap(x => x.split(” "))一致: b. 通过mapValues方法,直接作用于Map的value值,只改变val...
Python-深度学习-学习笔记(16):利用h5py对数据进行读写的方法
Python-深度学习-学习笔记(16):利用h5py对数据进行读写的方法 通常情况下,在神经网络训练的过程中有很多参数,有些参数需要我们后期进行处理,所以需要保存下来,这时我们就可以采用h5py库对数据集或者权重、偏差等一下参数进行保存为.h5文件。 1、数据的存储与读取 写入数据: 假设我这里有训练的到的准确率的数据,我需要将这些数据进行存储: 读取数据: 即可打印出结果。 2、训练模型的存储...
ISODATA算法
在kmean基础上分割,合并。多波段图像计算。 思想和网上ISODATA似乎有点不同,当聚类之间不满足条件需要合并或者分裂的时候直接减少增加分类个数,重新进行聚类计算。根据设置的阈值不同会有不同的计算结果。 运行截图: 原图 分类后图像: 分类结果k: 单波段分类截图: 分类数:7...
