头歌(Linux之进程管理一):第4关:进程终止

任务描述

在上一关我们学习使用vfork函数创建新进程,并且使用exit来结束子进程,本关我们将介绍Linux系统中结束进程的其它方法。

本关任务:学习终止进程的常见方法。

相关知识

在上一关以及看到,开发人员使用vfork创建出来的子进程可以用exit函数来结束。在 Linux 环境中,一个进程的结束,可以通过调用相应的函数实现,也可以是接收到某个信号而结束。

常见与退出进程相关的函数有:exit_exitatexiton_exitabortassert

  1. exit函数是标准C库中提供的函数,它用来终止正在运行的程序,并且关闭所有I/O标准流。
  2. _exit函数也可用于结束一个进程,与exit函数不同的是,_exit不会关闭所有I/O标准流。
  3. atexit 函数用于注册一个不带参数也没有返回值的函数以供程序正常退出时被调用。
  4. on_exit 函数的作用与atxeit函数十分类似,不同的是它注册的函数具有参数,退出状态和参数arg都是传递给该程序使用的。
  5. abort 函数其实是用来发送一个SIGABRT信号,这个信号将使当前进程终止。
  6. assert是一个宏。调用assert时,它将先计算参数表达式 expression的值,如果为0,则调用abort函数结束进程。

[exit_exit区别]

以上关于退出处理函数中只有_exit是系统调用函数,因此使用man 2 _exit来查看其使用方法,而其余函数都是库函数,因此使用man 3 函数名来查看其使用方法。

exit_exit使用方法

exit函数的具体的说明如下:

  • 需要的头文件如下:
    1. #include <stdlib.h>
  • 函数族格式如下:
    1. void exit(int status);
    参数说明: status:设置程序退出码;

_exit函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <unistd.h>
  • 函数族格式如下:

    1. void _exit(int status);

    参数说明: status:设置程序退出码;

  • 函数返回值说明: exit_exit均无返回值。

atexiton_exit使用方法

atexiton_exit函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <stdlib.h>
  • 函数族格式如下:

    1. int atexit(void (*function)(void));
    2. int on_exit(void (*function)(int , void *), void *arg);

    参数说明: atexit函数的function参数是一个函数指针,指向无返回值和无参数的函数; on_exit函数的function参数是一个函数指针,指向无返回值和有两个参数的函数,其中第一个参数是调用exit()或从main中返回时的值,参数arg指针会传给参数function函数;

  • 函数返回值说明: atexiton_exit调用成功返回0;调用失败返回一个非零值。

注意:atexiton_exit只有在程序使用exit或者main中正常退出时才会有效。如果程序使用_exitabortassert退出程序时,则不会执行被注册的函数。

案例演示1: 使用atexit注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. void out()
  4. {
  5. printf("程序正在被退出\n");
  6. }
  7. int main()
  8. {
  9. if(atexit(out) != 0)
  10. {
  11. printf("调用atexit函数错误\n");
  12. }
  13. return 0; //或者exit(0)
  14. }

将以上代码保存为atexit.c文件,编译执行。可以看到执行atexit程序后,out函数被调用。

案例演示2: 使用on_exit注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. void out(int status, void *arg)
  4. {
  5. printf("%s(%d)\n", (char *s)arg, status);
  6. }
  7. int main()
  8. {
  9. if(on_exit(out, "程序正在被退出") != 0)
  10. {
  11. printf("调用on_exit函数错误\n");
  12. }
  13. exit(1); //或者return 1
  14. }

将以上代码保存为on_exit.c文件,编译执行。可以看到执行on_exit程序后,out函数被调用,并且status变量的值就是exit函数退出的值。

abortassert使用方法

abort函数的具体的说明如下:

  • 需要的头文件如下:
    1. #include <stdlib.h>
  • 函数族格式如下:
    1. void abort(void);

assert宏的具体的说明如下:

  • 需要的头文件如下:
    1. #include <assert.h>
  • 函数族格式如下:
    1. void assert(scalar expression);
    参数说明: expression:需要被判断的表达式;

注意:assert宏通常用于调试程序。

  • 函数返回值说明: abortassert无返回值。

案例演示1: 使用abort终止一个程序,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main()
  4. {
  5. printf("Hello world\n");
  6. abort();
  7. }

将以上代码保存为abort.c文件,编译执行。可以看到执行abort程序后,程序被强行终止。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全exitProcess函数,使用atexit函数注册一个函数,在注册函数中打印出当前进程的ID号。

测试说明

本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>

/************************
 * 提示: 用户需要在exitProcess函数中使用atexit函数注册一个自定义函数,并在自定义函数中打印出当前进程ID号
*************************/
void out()
{
    printf("%d\n",getpid());
}
void exitProcess()
{
	/********** BEGIN **********/
    if(atexit(out)!=0)
    {
        printf("调用atexit函数错误\n");
    }
}

 

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线程概念 什么是线程 LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下) 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 区别:在于是否共享地址空间。 独居(进程);合租(线程)。 Linux下: 线程:最小的执行单位 进程:最小分配资源单位,可看成是只有一个线程的进程Linux内核线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程系密切。 1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone 2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的 3. 进程可以蜕变成线程 4. 线程可看做寄存器和栈的集合 5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位 察看LWP号:ps –Lf pid 查看指定线程的lwp号。 三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元 参考:《Linux内核源代码情景分析》 ----毛德操 对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。 但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。 实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。 如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。 因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。 线程共享资源 1.文件描述符表 2.每种信号的处理方式 3.当前工作目录 4.用户ID和组ID 5.内存地址空间 (.text/.data/.bss/heap/共享库) 线程非共享资源 1.线程id 2.处理器现场和栈指针(内核栈) 3.独立的栈空间(用户空间栈) 4.errno变量 5.信号屏蔽字 6.调度优先级 线程优、缺点 优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便 缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。 线程控制原语 pthread_self函数 获取线程ID。其作用对应进程中 getpid() 函数。 pthread_t pthread_self(void); 返回值:成功:0; 失败:无! 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同) 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。 pthread_create函数 创建一个新线程。 其作用,对应进程中fork() 函数。 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。 参数: pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t; 参数1:传出参数,保存系统为我们分配好的线程ID 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。 参数4:线程主函数执行期间所使用的参数。 在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。star
一个进程池的服务器程序 下面做了非常简单的http服务器,该服务器只能接收Get请求。 流程大概如下: 1,父进程listen,创建pipe(下面所有父子进程之间的通信都用该pipe) 2,父进程预fork n个子进程 3,各个子进程accept(listenfd),即所有子进程竞争accept请求。由于listenfd是在fork之前就有的,所以所有子进程都可以访问到,不需用到“进程间文件描述符传递”问题; 4,子进程每accept到一个请求都告诉父进程,父进程把请求数加1;子进程没完成一个请求,父进程把请求数减1;当父进程发现请求数 >= 子进程数时,父进程创建新的子进程,并把子进程数加1(当然子进程数有个预先上限);当父进程发现子进程数大于请求数加1时,父进程杀死多余的子进程。 总的来说,思想是让子进程accept并处理请求,父进程通过子进程发来的信息控制请求数与子进程数之间的系。 代码如下: 代码如下: #include <time.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <dirent.h> #include <sys/stat.h> #include <signal.h> #include <sys/wait.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #define PRECHILD 5 #define MAXCHILD 50 #define BUFSIZE 4096 #define PIDPATH "pid" #define head503 "HTTP/1.1 503 Service unavailable\r\n" #define head404 "HTTP/1.1 404 Not Found\r\n" #define head200 "HTTP/1.1 200 0K\n\rContent—Type: text/html\n\rContent—Length: " int len503, len404, len200; int fd1[2], fd2[2]; typedef struct { pid_t pid; char status; // 'n' means new request; 'f' means finish the request } REPORT; void answer(int listenfd) { int connfd; char buf[BUFSIZE]; int count; int pid = getpid(); struct sockaddr_in cliaddr; int size = sizeof(cliaddr); char comm; REPORT rep; rep.pid = pid; while (1) { connfd = accept(listenfd, (struct sockaddr *)&cliaddr,(socklen_t *)&size ); //子进程accept请求 rep.status = 'n'; if (write(fd1[1], &rep, sizeof(rep)) < 0) { //通知父进程已经accept了请求 perror("write pipe new failed"); exit(-1); } count = read(connfd, buf, BUFSIZE); char req[10]; char filepath[256]; sscanf(buf, "%s%s", req, filepath + 1); filepath[0] = '.'; if (strcmp("GET", req) != 0) {//503 write(connfd, head503, len503); //goto err_out; close(connfd); exit(-1); } char content[BUFSIZE]; struct stat stbuf; if (lstat(filepath, &stbuf) != 0) { int err = errno; if (err == ENOENT) {//404 write(connfd, head404, len404); } close(connfd); exit(-1); } count = write(connfd, head200, len200); u_int filesize = stbuf.st_size; sprintf(content, "%u\n\r\n\r", filesize); count = write(connfd, content, strlen(content)); FILE *fp = fopen(filepath, "r"); if (fp == NULL) { printf("open file %s failed\n", filepath); close(connfd); exit(-1); } while((count = fread(content, 1, sizeof(content), fp)) > 0) { //printf("%s", content); if (write(connfd, content, count) != count) { printf("write failed\n"); } } fclose(fp); close(connfd); rep.status = 'f'; if (write(fd1[1], &rep, sizeof(rep)) < 0) {//告诉父进程自己处理完了请求 perror("write pipe finish failed"); exit(-1); } if (read(fd2[0], &comm, 1) < 1) {//等待来自父进程的命令 perror("read pipe failed"); exit(-1); } //printf("[%d] reve %c from pa\n", pid, comm); if (comm == 'e') { //收到exit命令 printf("[%d] exit\n", pid); exit(-1); } else if (comm == 'c') { //收到继续accept的命令 printf("[%d] continue\n", pid); } else { printf("[%d] comm : %c illeagle\n", pid, comm); } } } void usage() { printf("Usage: http-serv port\n"); } int write_pid() { int fd; if ((fd = open(PIDPATH, O_WRONLY | O_TRUNC | O_CREAT, S_IWUSR)) < 0){ perror("open pidfile faild"); return -1; } struct flock lock; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { int err = errno; perror("fcntl faild"); if (err == EAGAIN) { printf("Another http-serv process is running now!\n"); } return -1; } return 0; } void daemon_init() { //clear file creation mask; umask(0); //become a session leader if (fork() != 0) exit(-1); if (setsid() < 0) exit(-1); //make sure can be never get the TTY control if (fork() != 0) exit(-1); //may chdir here int i; for (i = 0; i < 1024; i++) close(i); /* * Attach file descriptors 0, 1, and 2 to /dev/null. */ int fd0, fd1, fd2; fd0 = open("/dev/null", O_RDWR); fd1 = dup(0); fd2 = dup(0); if (fd0 != 0 || fd1 != 1 || fd2 != 2) { printf("init failed\n"); exit(-1); } } int main(int argc, char **argv) { int listenfd; struct sockaddr_in servaddr; pid_t pid; if (argc != 2) { usage(); return -1; } signal(SIGCHLD, SIG_IGN); len200 = strlen(head200); len404 = strlen(head404); len503 = strlen(head503); daemon_init(); //转为后台程序,如需打印调试,把这行注释掉 if (write_pid() < 0) //避免同时有多个该程序在运行 return -1; if (pipe(fd1) < 0) { perror("pipe failed"); exit(-1); } if (s_pipe(fd2) < 0) { perror("pipe failed"); exit(-1); } int port = atoi(argv[1]); //initialize servaddr and listenfd... bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); listenfd = socket(AF_INET, SOCK_STREAM, 0); bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); listen(listenfd, 1000); int i; for (i = 0; i < PRECHILD ; i++) { //父进程预fork 子进程 if ((pid = fork()) < 0) { perror("fork faild"); exit(3); } else if (pid == 0) { answer(listenfd); } else { printf("have create child %d\n", pid); } } char e = 'e'; char c = 'c'; int req_num = 0; int child_num = PRECHILD; REPORT rep; while (1) { //printf("req_num = %d, child_num = %d\n", req_num, child_num); if (read(fd1[0], &rep, sizeof(rep)) < sizeof(rep)) {//等待子进程发来消息 perror("parent read pipe failed"); exit(-1); } //printf("parent: receive from %d\n", pid); if (rep.status == 'n') {//子进程刚accept了新的请求 req_num ++; printf("parent: %d have receive new request\n", rep.pid); if (req_num >= child_num && child_num <= MAXCHILD) { //请求数过多,创建更多子进程 if ((pid = fork()) < 0) { perror("fork faild"); exit(3); } else if (pid == 0) { answer(listenfd); } else { printf("have create child %d\n", pid); child_num ++; } } } else if (rep.status == 'f') {//子进程刚处理完了一个请求 req_num --; //printf("parent: %d have finish a request\n", rep.pid); if (child_num > (req_num + 1) && child_num > PRECHILD) {//子进程数过多,删除多余的子进程 if (write(fd2[1], &e, sizeof(e)) < sizeof(e)) { perror("pa write pipe failed"); exit(-2); } //printf("tell child exit\n"); child_num --; } else { if (write(fd2[1], &c, sizeof(c)) < sizeof(c)) {//让子进程继续等待accept perror("pa write pipe failed"); exit(-2); } //printf("tell child continue\n"); } } } return 0; } 利用fork()创建多个子进程 11:09 pm on Oct 23rd 2010 greenMay 之间我学习了创建一个子进程,也大致理解了子进程与父进程系。今天无意间遇到一个创建多个子进程的问题,结果还发现了点小bug,现在写下来和大家分享。 我需要实现的目标如下:编写一段源程序,使系统调用fork()创建两个子进程,当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。 一开始我的主要代码如下: view source print? 01 int main() 02 { 03 pid_t child1; 04 pid_t child2; 05 child1 = fork(); 06 child2 = fork(); 07 if(child1 == 0) 08 { 09 printf("Child1:a\n"); 10 return 0; 11 } 12 if(child2 == 0) 13 { 14 printf("Child2:b\n"); 15 return 0; 16 } 17 else 18 { 19 waitpid(child1,NULL,0); 20 waitpid(child2,NULL,0); 21 printf("Parent:c\n"); 22 } 23 return 0; 24 } 奇怪的是,我得到的是这样一个结果: Child1:a Child1:a Child2:b Parent:c 竟然有两个Child1。可是我的代码里明明只是让Chidl1打印一次啊。搜索到一篇好的博文。文章仔细分析了和我几乎相同的情况。事实上,是我的粗心和对fork()的理解不深刻导致了上述的奇怪问题。 我们知道,fork()之后,我们还是首先执行的是父进程,也就是如下代码段: view source print? 1 waitpid(child1,NULL,0); 2 waitpid(child2,NULL,0); 3 printf("Parent:c\n"); 然后waitpid(child1,NULL,0),进入child1的执行。child1将要执行的是如下的的代码段: view source print? 1 child2 = fork(); 2 if(child1 == 0) 3 { 4 printf("Child1:a\n"); 5 return 0; 6 } 注意,第一行的那个child2 = fork()!这就意味着对于child1来说,它自己又要创建一个子进程,这时候他成为了父亲。这时候,它有一个儿子child2,但是这个child2不同与我们刚才定义的那个child2,这个child2其实是parent的孙子。之所以又打印了一边Child1。如果加上如下代码就明白了: view source print? 01 child2 = fork(); 02 if(child1 == 0) 03 { 04 if(child2 == 0) 05 { 06 printf("GrandChild!\n"); 07 } 08 printf("Child1:a\n"); 09 return 0; 10 } 这时候将出现: Child1:a GrandChild! Child1:a Child2:b Parent:c 恩,这就很明白了!我无意间多调用了一次child2=fork(); 所以,如果要达到我最初的目的,需要改变child2的fork()的位置: view source print? 01 #include <stdio.h> 02 #include <unistd.h> 03 #include <sys/types.h> 04 #include <sys/wait.h> 05 int main() 06 { 07 pid_t child1; 08 pid_t child2; 09 child1 = fork(); 10 child2 = fork(); 11 if(child1 == 0) 12 { 13 printf("Child1:a\n"); 14 return 0; 15 } 16 if(child2 == 0) 17 { 18 printf("Child2:b\n"); 19 return 0; 20 } 21 else 22 { 23 waitpid(child1,NULL,0); 24 waitpid(child2,NULL,0); 25 printf("Parent:c\n"); 26 } 27 return 0; 28 } 我参照的那个博文最后给出了一个更为普遍的fork()创建多进程的程序框架: view source print? 01 pid_t create_child() 02 { 03 pid_t p = fork(); 04 if( p == 0 ) 05 { 06 printf("in child %d\n", getpid()); 07 //do something 08 return 0; 09 } 10 return p; 11 } 12 int main(void) 13 { 14 pid_t p1 = create_child(); 15 pid_t p2 = create_child(); 16 17 int st1, st2; 18 waitpid( p1, &st1, 0); 19 waitpid( p2, &st2, 0); 20 printf("in parent, pid = %d\n", getpid()); 21 printf("in parent, child 1 exited with %d\n", st1); 22 printf("in parent, child 2 exited with %d\n", st2); 23 return 0; 24 } 注意到,期中的create_child()函数最后有一个return p。这个return p将pid返回给了父进程,其实也是将子进程对于CPU的控制权交还给了父进程,这样就避免了多个子进程在创建之时互相影响了。 可以说,今天的这个问题真是一个有趣的事情。代码有的时候就是这么奇怪~ 最后,向我引用的那篇文章致敬! Linux内核对多进程和多线程的支持方式: 线程机制支持并发程序设计技术,在多处理器上能真正保证并行处理。而在linux实现线程很特别,linux把所有的线程都当作进程实现。linux下线程看起来就像普通进程(只是该进程和其他进程共享资源,如地址空间)。上述机制与Microsoft windows或是Sun Solaris实现差异很大。 Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。 do_fork() 提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用产生多进程时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。当使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的”进程”拥有共享的运行环境,只有栈是独立的,由 __clone()传入。 即:Linux下不管是多线程编程还是多进程编程,最终都是用do_fork实现的多进程编程,只是进程创建时的参数不同,从而导致有不同的共享环境。Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager() ,每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相的信号,而主线程pthread_create()) 的调用者则通过管道将请求信息传给管理线程。 很多朋友都说使用多线程的好处是资源占用少,其隐含之意就是说进程占用资源比线程多,对吧?但实际上Linux下多进程是否就真的点用很多资源呢?暂且不说进程是否比线程占用资源多,就进程占用资源的多少情况而言,Linux确实是做得相当节省的。产生一个多进程时肯定是要产生的一点内存是要复制进程表项,即一个task_struct结构,但这个结构本身做得相当小巧。其它对于一个进程来说必须有的数据段、代码段、堆栈段是不是全盘复制呢?对于多进程来说,代码段是肯定不用复制的,因为父进程和各子进程的代码段是相同的,数据段和堆栈段呢?也不一定,因为在Linux里广泛使用的一个技术叫copy-on-write,即写时拷贝。copy-on-write意味着什么呢?意味着资源节省,假设有一个变量x在父进程里存在,当这个父进程创建一个子进程或多个子进程时这个变量x是否复制到了子进程的内存空间呢?不会的,子进程和父进程使用同一个内存空间的变量,但当子进程或父进程要改变变量x的值时就会复制该变量,从而导致父子进程里的变量值不同。父子进程变量是互不影响的,由于父子进程地址空间是完全隔开的,变量的地址可以是完全相同的。 Linux的”线程”和”进程”实际上处于一个调度层次,共享一个进程标识符空间,这种限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。Linux进程的创建是非常迅速的。内核设计与实现一书中甚至指出Linux创建进程的速度和其他针对线程优化的操作系统(Windows,Solaris)创建线程的速度相比,测试结果非常的好,也就是说创建速度很快。由于异步信号是内核以进程为单位分发的,而LinuxThreads的每个线程对内核来说都是一个进程,且没有实现”线程组”,因此,某些语义不符合POSIX标准,比如没有实现向进程中所有线程发送信号,README对此作了说明。LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。LinuxThreads 的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得Linux上的线程应用一直保持在比较低的水平。在Linux社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有两个项目,一个是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另一个则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重起炉灶全新设计的。 综上所述的结论是在Linux下编程多用多进程编程少用多线程编程。 IBM有个家伙做了个测试,发现切换线程context的时候,windows比linux快一倍多。进出最快的锁(windows2k的 critical section和linux的pthread_mutex),windows比linux的要快五倍左右。当然这并不是说linux不好,而且在经过实际编程之后,综合来看我觉得linux更适合做high performance server,不过在多线程这个具体的领域内,linux还是稍逊windows一点。这应该是情有可原的,毕竟unix家族都是从多进程过来的,而 windows从头就是多线程的。 如果是UNIX/linux环境,采用多线程没必要。 多线程比多进程性能高?误导! 应该说,多线程比多进程成本低,但性能更低。 在UNIX环境,多进程调度开销比多线程调度开销,没有显著区别,就是说,UNIX进程调度效率是很高的。内存消耗方面,二者只差全局数据区,现在内存都很便宜,服务器内存动辄若干G,根本不是问题。 多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。 多线程是平面交通系统,造价低,但红绿灯太多,老堵车。 我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。 高性能交易服务器中间件,如TUXEDO,都是主张多进程的。实际测试表明,TUXEDO性能和并发效率是非常高的。TUXEDO是贝尔实验室的,与UNIX同宗,应该是对UNIX理解最为深刻的,他们的意见应该具有很大的参考意义 1. 散沙 2010年7月10日08:43 回复 | 引用 | #1 文章很有深度,我们把握一个尺度就可以了,在windows下使用线程,unix下则使用进程就可以了 2. rjoo 2010年9月9日13:49 回复 | 引用 | #2 错的太多了,博主,应该看看新资料了。 现在都2010年了,NPTL早就取代了老的Linux thread。而且通常多线程有性能优势,但是多进程更稳定,并且通常性能瓶颈不在于是进程模型还是线程模型而在于IO。 3. rjoo 2010年9月9日13:56 回复 | 引用 | #3 于那个critical section和pthread_mutex_t,critical section本质上是一个自旋锁,短期锁当然快,不知道你说的那个IBM的哥们怎么比的,要比也该是和pthread_spinlock_t比。 4. admin 2010年9月9日17:28 回复 | 引用 | #4 rjoo挺热心的,呵呵,这篇文章不是我写的,但有几个地方我可以解答一下: 1. Linux下没有线程的概念,pthread线程实质是通过轻量级进程实现的。你说瓶颈在IO,这一点我很赞同你的意见,作者如果能再写个IO操作的文章来的话就会更好了。 2. mutex和critical section的确是不能比的。一个涉及到内核,一个没有涉及到内核。呵呵,很佩服你对这些东西的掌握程度,有机会多交流。 ^_^ 5. 定时 2010年9月9日17:40 回复 | 引用 | #5 我们组的最近项目的经验告诉我们能用多进程不用多线程,多线程安全编程难,而且锁会早成效率很低,甚至不如单线程,你说的NPTL我知道,他只是多线程优化了并不能改变多线程安全编程的问题,锁的问题。谢谢指教,实践出真知。 @rjoo 6. 定时 2010年9月9日17:44 回复 | 引用 | #6 你说的锁,我确实不太了解,但是我们leader对它很了解,就是最近的一个项目,锁搞得他很郁闷,他也终于同意我的键,尽可能不用多线程。 @rjoo 7. rjoo 2010年9月29日13:41 回复 | 引用 | #7 @admin Linux下没有线程的概念,pthread线程实质是通过轻量级进程实现的—这是2.4内核以前的情况(实际上是2.0时引入的,那可实在是太久了),2.4内核引入NGPL,2.6内核线程支持改为NPTL。NPTL实现的是1:1的线程模型(有资料说Win也是这种实现,虽然不太确定,但我觉得可能性很大),而NGPT虽然是理论上最先进的m:n线程模型,但最后实现出来的性能差NPTL一大截,最后被抛弃。看看文中说法就知道要么文章写的很早,要么作者看了一堆十年前的资料。 给个链接: http://www.kegel.com/c10k.html#threads.linuxthreads 8. finalday 2010年10月15日17:26 回复 | 引用 | #8 忍不住跳出来说,作者对并发编程的理解还不行。 比如说锁的问题,说得好像是多线程才需要的东西一样。如果一个应用多进程时完全不用锁,多线程也就多一个轻量级锁——锁一下,各回各家,每个线程用自己的专有存储,之后不就和多进程一样了?这样会被搞得很郁闷?当然不会。所以说明那个应用对于数据共享的需求不是这么简单,既然不是这么简单,多进程程序一样要加锁。多进程的加解锁代价可比多线程大得多了,共享数据和协作也麻烦多了。 多线程编程难不难?难,但这是由于并发本身的难度引起的。“锁”,“安全编程”不管是多线程还是多进程都一样会遇到。 多线程的最大优点是数据共享和协作方便。 多进程的最大优点是挂了一个进程不会影响其他进程,资源也不会泄露,故比较能容忍程序员犯错。 至于两者裸奔比性能,真的没啥意义。
Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相函数 POSIX互斥锁相函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值