【面试常见题目之后台知识】Linux多线程与多进程区别全方位解读+程序实例

【概念上】  

        一 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位;

  二 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程;

【进程与线程区别与联系】

  (1) 划分尺度:线程更小,所以多线程程序并发性更高;

  (2) 资源分配:进程是资源分配的基本单位,同一进程内多个线程共享其资源;

  (3) 地址空间:进程拥有独立的地址空间,同一进程内多个线程共享其资源;

  (4) 处理器调度:线程是处理器调度的基本单位;

  (5) 执行:每个线程都有一个程序运行的入口,顺序执行序列和程序的出口,但线程不能单独执行,必须组成进程,一个进程至少有一个主线程。简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

【linux上的进程与线程】

一般来说,线程是windows上的概念,windows区分进程和线程。而在linux(老版本)上,统一叫进程,进程是完成某项任务所需资源的集合,同时也是linux基本的执行单元。

Linux线程是通过进程来实现。Linux kernel为进程创建提供一个clone()系统调用,clone的参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。通过clone()的参数,新创建的进程,也称为LWP(Lightweight process)与父进程共享内存空间,文件句柄,信号处理等,从而达到创建线程相同的目的。

Linux 2.6的线程库叫NPTL(Native POSIX Thread Library)POSIX thread(pthread)是一个编程规范,通过此规范开发的多线程程序具有良好的跨平台特性。尽管是基于进程的实现,但新版的NPTL创建线程的效率非常高。一些测试显示,基于NPTL的内核创建10万个线程只需要2秒,而没有NPTL支持的内核则需要长达15分钟。

Linux 2.6之前,Linux kernel并没有真正的thread支持,一些thread library都是在clone()基础上的一些基于user space的封装,因此通常在信号处理、进程调度(每个进程需要一个额外的调度线程)及多线程之间同步共享资源等方面存在一定问题。为了解决这些问题,当年IBM曾经开发一套NGPT(NextGeneration POSIX Threads), 效率比 LinuxThreads有明显改进,但由于NPTL的推出,NGPT也完成了相关的历史使命并停止了开发。

NPTL的实现是在kernel增加了futex(fast userspace mutex)支持用于处理线程之间的sleepwakefutex是一种高效的对共享资源互斥访问的算法。kernel在里面起仲裁作用,但通常都由进程自行完成。

NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是非常简单。而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体。(N<M),优点是线程切换快,但实现稍复杂。

【执行环境】

每个执行环境都有自己的状态,包括CPU状态,内存映射状态,权限状态(uid,pid)和各种各样的通信状态(打开的文件描述符,管道,信号处理程序等)。

而线程和进程的不同在于线程这种执行环境之间共享了很多状态,至于共享哪些状态不取决于书上说了什么(书上只说了一点点),而是取决于创建这个执行环境的函数提供了哪些选择。

在linux下,线程通过clone这个函数创建,而这个函数和fork一样,其实是用来创建进程的,但是clone在创建进程的时候,会指定调用这个函数的进程和新创建的进程之间共享的内容,比如说pid,代码段,文件描述符等等,所以看上去好像一个进程里面有很多股进程在并行运行,这些并行运行的进程后来有了个新名字:线程。所以大家不必太在意他们叫什么名字,就好比给皇上吃的胡萝卜叫“宫廷胡萝卜”一样,换个名字都是唬人的,只要搞清楚实质就好了。

而我们创建新线程(进程)也可以不和老进程共享那些内容,比如不共享文件描述符,这样线程(子进程)可以打开的文件数就不会受到父进程的限制了(通常一个进程可以打开的文件数是有限制的,可以使用ulimit -n查看,一般是1024);再比如不共享pid,这样新线程也有自己的pid(进程id),从这也可以看出线程其实就是进程。

【内存中】

概念缕清楚了,后面的内容还是回到默认写法,大家看到线程、进程,心里清楚其实就是COE就好了。

多线程程序和普通程序在内存中的的不同主要表现在栈的不同,每个线程一个栈,所以线程的局部变量不会受其他线程影响。而.text, .data, .bss, 等部分各个线程是共享的,所以线程间通信非常简单,直接在这些共享的内存中存取就可以了,所谓祸福相依,线程内操作这些共享内存中的数据的时候,也需要非常小心,要确保只有当前线程在操作全局变量(通过锁或者其他一些操作)。

再就没什么好说的了,感觉上一篇博客已经讲的比较清楚了,最后就是一个示例程序了:

【程序实例】

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>


void *thread_foo_func(void *);
void *thread_bar_func(void *);


int global = 4;


int main(){
  int local = 8;
  int foo, bar;
  pthread_t fthread, bthread;
  foo = pthread_create(&fthread, NULL, thread_foo_func, (void *)&local);
  bar = pthread_create(&bthread, NULL, thread_bar_func, (void *)&local);
  if (foo != 0 || bar != 0){
    printf("thread creation failed.\n");
    return -1;
  }


  foo = pthread_join(fthread, NULL);
  bar = pthread_join(bthread, NULL);
  if (foo != 0 || bar != 0){
    printf("thread join failed.\n");
    return -2;
  }


  char i;
  scanf("%c", &i);


  return 0;
}


void *thread_foo_func(void *arg){
  int foo_local = 16;
  printf("address of global %d: %x\n", global, &global);
  printf("address of main local %d: %x\n", *(int *)arg, arg);
  printf("address of foo local: %x\n", &foo_local);
  char i;
  scanf("%c", &i);
}


void *thread_bar_func(void *arg){
  int bar_local = 32;
  printf("address of global %d: %x\n", global, &global);
  printf("address of main local %d: %x\n", *(int *)arg, arg);
  printf("address of bar local: %x\n", &bar_local);
  char i;
  scanf("%c", &i);
}

运行结果如下:

~/interview$ ./a.out 
address of global 4: 601048
address of main local 8: 4beec428
address of foo local: 639fee88
address of global 4: 601048
address of main local 8: 4beec428
address of bar local: 631fde88

~/interview$ cat /proc/6470/maps 
00400000-00401000 r-xp 00000000 08:05 396514                             /home/zhangshijun/interview/a.out
00600000-00601000 r--p 00000000 08:05 396514                             /home/zhangshijun/interview/a.out
00601000-00602000 rw-p 00001000 08:05 396514                             /home/zhangshijun/interview/a.out
01597000-015b8000 rw-p 00000000 00:00 0                                  [heap]
7ffc629fe000-7ffc629ff000 ---p 00000000 00:00 0 
7ffc629ff000-7ffc631ff000 rw-p 00000000 00:00 0 
7ffc631ff000-7ffc63200000 ---p 00000000 00:00 0 
7ffc63200000-7ffc63a00000 rw-p 00000000 00:00 0 
7ffc63a00000-7ffc63b7a000 r-xp 00000000 08:07 21757969                   /lib/libc-2.11.1.so
7ffc63b7a000-7ffc63d79000 ---p 0017a000 08:07 21757969                   /lib/libc-2.11.1.so
7ffc63d79000-7ffc63d7d000 r--p 00179000 08:07 21757969                   /lib/libc-2.11.1.so
7ffc63d7d000-7ffc63d7e000 rw-p 0017d000 08:07 21757969                   /lib/libc-2.11.1.so
7ffc63d7e000-7ffc63d83000 rw-p 00000000 00:00 0 
7ffc63d83000-7ffc63d99000 r-xp 00000000 08:07 21758007                   /lib/libgcc_s.so.1
7ffc63d99000-7ffc63f98000 ---p 00016000 08:07 21758007                   /lib/libgcc_s.so.1
7ffc63f98000-7ffc63f99000 r--p 00015000 08:07 21758007                   /lib/libgcc_s.so.1
7ffc63f99000-7ffc63f9a000 rw-p 00016000 08:07 21758007                   /lib/libgcc_s.so.1
7ffc63f9a000-7ffc6401c000 r-xp 00000000 08:07 21757973                   /lib/libm-2.11.1.so
7ffc6401c000-7ffc6421b000 ---p 00082000 08:07 21757973                   /lib/libm-2.11.1.so
7ffc6421b000-7ffc6421c000 r--p 00081000 08:07 21757973                   /lib/libm-2.11.1.so
7ffc6421c000-7ffc6421d000 rw-p 00082000 08:07 21757973                   /lib/libm-2.11.1.so
7ffc6421d000-7ffc64313000 r-xp 00000000 08:07 111412749                  /usr/lib/libstdc++.so.6.0.13
7ffc64313000-7ffc64513000 ---p 000f6000 08:07 111412749                  /usr/lib/libstdc++.so.6.0.13
7ffc64513000-7ffc6451a000 r--p 000f6000 08:07 111412749                  /usr/lib/libstdc++.so.6.0.13
7ffc6451a000-7ffc6451c000 rw-p 000fd000 08:07 111412749                  /usr/lib/libstdc++.so.6.0.13
7ffc6451c000-7ffc64531000 rw-p 00000000 00:00 0 
7ffc64531000-7ffc64549000 r-xp 00000000 08:07 21757983                   /lib/libpthread-2.11.1.so
7ffc64549000-7ffc64748000 ---p 00018000 08:07 21757983                   /lib/libpthread-2.11.1.so
7ffc64748000-7ffc64749000 r--p 00017000 08:07 21757983                   /lib/libpthread-2.11.1.so
7ffc64749000-7ffc6474a000 rw-p 00018000 08:07 21757983                   /lib/libpthread-2.11.1.so
7ffc6474a000-7ffc6474e000 rw-p 00000000 00:00 0 
7ffc6474e000-7ffc6476e000 r-xp 00000000 08:07 21757963                   /lib/ld-2.11.1.so
7ffc64960000-7ffc64964000 rw-p 00000000 00:00 0 
7ffc64969000-7ffc6496d000 rw-p 00000000 00:00 0 
7ffc6496d000-7ffc6496e000 r--p 0001f000 08:07 21757963                   /lib/ld-2.11.1.so
7ffc6496e000-7ffc6496f000 rw-p 00020000 08:07 21757963                   /lib/ld-2.11.1.so
7ffc6496f000-7ffc64970000 rw-p 00000000 00:00 0 
7fff4bed8000-7fff4beed000 rw-p 00000000 00:00 0                          [stack]
7fff4bfff000-7fff4c000000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

关键看看几个局部变量的内存的范围,main函数的局部变量地址是:4beec428 ,在main函数栈的范围内:7fff4bed8000-7fff4beed000 

foo的局部变量在foo的栈里面,内存范围是:7ffc63200000-7ffc63a00000。

bar的局部变量在bar的栈里面,内存范围是:7ffc629ff000-7ffc631ff000。


参考文献:

http://blog.csdn.net/high_high/article/details/7204097

 http://www.kernel.org/doc/man-pages/index.html

Implementing a Thread Library on Linux

linus本人发的一个帖子,值得一看,猛击进入

Professional Linux kernel architecture. Wolfgang Mauerer. 2008



你可能感兴趣的:(JOIN,thread,多线程,linux,面试,library)