linux下的多进程,多线程编程

关于多进程和多线程,一直想写点什么来进行一次总结,今天终于提笔了,若有讲解错误之处,希望广大读者能给予指正。,我想从以下几个方面进行一次详解划分.第一,运用。第二,同步。第三,通信。第四,选择。

那么闲话少说,开始第一个,关于线程和进程之间的运用。

什么是进程?

有一个很官方的说法:进程是程序在计算机上的一次执行活动。但我觉得,可能这句话有点不对,应该换成进程加线程是程序在计算机上的一次执行活动才更加的合理。因为进程是资源分配的最小单位,线程是CPU调度的最小单位。所以我们看到进程的时候,应该联想到资源,看到线程的时候联想到程序的执行,程序真正再跑的只是线程,例如程序开始的时候,就是一个线程利用进程的资源再跑。

打印出进程ID和主线程ID。

如何创建进程?

进程的创建,我们最容易想象到的两个函数是fork和vfork。

fork在创建的子进程复制了父进程的资源,新旧进程使用同一个代码段,复制了数据段和堆栈段,但是当进程开始运行的时候,新旧的地址空间在物理内存上开始划分开来,这里使用了copy_on_write技术来达到真正意义上的分开。两者独立运行,也就会在数据段和堆栈段上的数据不会有冲突。fork的优点取决父子进程完全独立,这样具有很好的并发性。

vfork在创建子进程的时候子进程是完全运行在父进程的地址空间上,使用vfork创建的子进程,笔者在这强烈建议使用exec函数族,因为使用exec函数族,执行了另一个程序,子进程不会对父进程的地址空间有任何引用,若需求上面不能执行exec函数族,那么子进程必须使用_exit()函数进行退出,也不能是执行exit()函数,因为exit函数会将标准输入输出流进行刷新,释放所占有的资源以及清空缓冲区,而_exit()没有刷新缓冲区功能,这里是考虑到vfork父子进程使用同一个内存空间,不然程序会出现意料之外的结果,比如如下图所示程序就出现三个进程,两个子进程和一个父进程,只有当子进程执行了_exit(),exec函数族等函数的时候,父进程才会正常执行。所以在此建议读者,如果需要使用exec函数族的时候,就用vfork()创建,不然就用fork()创建子进程.

运行结果是

num=0
子进程pid=29356
num=7917675
父进程pid=29355
num=0
子进程pid=29360

进程的等待,父进程可以用
wait和waitpid函数等待子进程的结束,wait是等待 任意一个子进程结束,waitpid 可以对等待的方式进行设置。如果没有
子进程,直接返回。如果没有结束的子进程, 父进程将被 阻塞(waitpid可以设置非阻塞)。wait、waitpid都可以取得 子进程的结束状态。pid_t
wait(int *status),返回结束的子进程id,参数用于带出结束状态,pid_t waitpid(pid_t pid,int* status,int
options),返回结束的子进程id,参数status用于 带出结束状态,options可以指定非阻塞,一般用0代表阻塞。结束状态 status可以用
宏做一些判断:WIFEXITED(status) 可以判断子进程是否正常结束。

   #include<pthread.h>
   #include<stdio.h>
   #include<stdlib.h>
   #include<signal.h>
   int main(int argc,char* argv[])
   {
       int status=0;
       pid_t pid=fork();
       if(pid<0)
       {
           perror("fork");
           exit(1);
       }
       else if(pid==0)
       {
           sleep(1);//让进程进行休眠状态,保证父进程先执行
           printf("子进程pid=%d\n",getpid());
           exit(5);
       }
       else
       {
           wait(&status);//程序进行阻塞状态,看其是否是等待子进程执行完毕
           printf("父进程pid=%d,%d\n",getpid(),status);
           if(WIFEXITED(status))
           {
               printf("the child process exit normally\n");
           }
           else
           {
               printf("the child process exit abnormally\n");
           }
       }
   }
<span style="font-family:KaiTi_GB2312;"><span style="font-size:18px;">
</span></span>

进程的结束,进程的结束可以分程序执行完毕,自然结束,也可以由int kill(pid_t pid, int sig);向指定的进程号发送信号,如果发送9信号,默认是杀掉指定的进程,因为考虑到资源问题,担心一些动态内存在进程里面申请后,正准备释放之前该进程被其它进程kill了,导致出现内存泄露,所以我们尽量避免使用kill这样暴力的函数

父子进程之间的执行,注在线程里面没有父子线程关系,只有主线程和其它线程.父进程启动子进程后,父子进程 同时运行。如果子进程先结束,子进程
给父进程发信号,父进程 负责回收子进程的资源。 父进程启动子进程后,父子进程 同时运行。如果父进程先结束,子进程成 孤儿进程,子进程 认
init进程(进程1)做新的父进程,init进程 也叫 孤儿院。父进程启动子进程后,父子进程 同时运行。如果子进程先结束,子进程
给父进程发信号,但父进程有可能没收到信号,或者收到了没处理,子进程 会变成 僵尸进程

线程的创建,使用函数int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg);第一个参数是指向线程标示符的指针,线程标示符,一个无符号的长整型的类型,在头文件里面我们可以看到它的定义typedef
unsigned long int
pthread_t;第二个参数是指向线程属性的指针,这个如若不设置属性,那么设置为空,一切就以默认属性,笔者觉得这个属性比较重要,将在后面详细讲解.第三个参数,顾名思义是一个函数指针,函数的返回值类型是无符号的指针类型,第四个参数是函数指针函数的传递参数,如若没有参数,则可以设置空,如果需要传递多个参数,可定义结构体进行传递,不过传递的时候要强制转换成void*类型。

线程的等待,使用函数pthread_join(),该函数是当线程结束的时候,清收线程的资源,然后函数返回,这里可以保证不会存在主线程结束了,开启的线程还么开始执行,或者只是执行了一半,有读者可能会有疑问,不是说线程是执行单位,进程才是资源单位,为什么还有清理资源工作,这里我们需要了解到其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用函数clone()。该系统调用copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。
copy后的进程和原先的进程共享了所有的变量(有点像是vfork的功能),运行环境。这样既可以做到多线程之间共享全局变量。所以pthread_join函数保证了线程的全部执行和资源清理工作。

线程的属性,也就是在创建线程函数pthread_create里面的第三个参数pthread_attr_t
*attr,首先我们要知道,pthread_attr_t是一个结构体类型,如下所示

typedef
struct
                     {
                       
int                          detachstate;     线程的分离状态
                      
int                          schedpolicy;   线程调度策略
                      
struct sched_param              schedparam;   线程的调度参数
                      
int                          inheritsched;    线程的继承性
                      
int                          scope;          线程的作用域
                      
size_t                        guardsize; 线程栈末尾的警戒缓冲区大小
                      
int                          stackaddr_set;
                        void
*                        stackaddr;      线程栈的位置
                      
size_t                        stacksize;       线程栈的大小
               
}pthread_attr_t;

属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。在这里就注重讲解堆栈大小属性的设置及其应用,至于其它属性值,读者若是有兴趣,可以参考http://blog.csdn.net/zsf8701/article/details/7843837,这篇博客.在很多的时候,我们需要增加并发数量来完成某一项认为,特别是在网路编程里面服务端程序上支持多并发处理,我们需要更多的并发量去支持,我们假设使用的计算机CPU是32位数,最大的寻址范围是4GB,1GB是所有的进程共享的内核空间,3GB是用户空间,也就是进程虚拟内存空间,我们在linux下面用ulimit
-s 可以产看当前系统上线程的堆栈空间大小是多少,一般默认是8M,这样我们可以计算出线程的最大上限数量是: (1024*1024*1024*3) /
(1024*1024*8) =
384,实际数字应该略小384,因为还要计算程序文本、数据、共享库等占用的空间。当前的WEB服务器上面并发量大于384已经是很平常的事了,那么我们如何突破这个限制,已达到更多的并发量呢,有两种办法,第一是通过ulimit
-s 去修改堆栈的空间大小,比如 ulimit -s 1024 
每个线程只分配1M的空间,那么并发量就可以增加到384*8,可以根据自己的项目需求去更改这个限制,但是这样做的缺点是修改了,那么所有在本机器上面跑的进程,都会默认这个设置,就会让一些进程因为堆栈空间太小导致段错误,所以显然这个不是一个很理智的办法。于是我们就有了线程设置堆栈大小的属性,保证本次受影响的线程只是设置这个属性的线程。说了这么多,我们就开始用实际代码练练手。

#include<stdio.h>
#include<string.h>
#include<limits.h>
#include<pthread.h>
void* fun(void* i);
int main(int argc,char* argv[])
  {
     pthread_t t[1000];
     pthread_attr_t attr;
     int i=0;
    
     if(pthread_attr_init(&attr))//初始化一个属性
      {
         perror("attr");
     }
    
     if(pthread_attr_setstacksize(&attr,163840))//设置线程的堆栈大小,我们开始利用初始化的属性进行堆栈空间大小的设置,这里是以字节为单位的堆栈空间大小的调整
      {
         perror("attr\n");
     }

     for(i=0;i<1000;i++)
     {
         pthread_create(&t[i],&attr,fun,NULL);//开启一个线程
      }

      for(i=0;i<1000;i++)
     {
        pthread_join(t[i],NULL);
     }
    printf("%d\n",PTHREAD_STACK_MIN);//这里需要牵扯到一个知识点,我们在设置堆栈空间大小的时候,不能小于linux内部定义的最小堆栈大小,可以打印这个宏看看是多少,根据操作系统不同而不同,我这里是16384
 }
 void* fun(void*i)
 {
 }

在设置属性值的时候,注意点是第一先利用pthread_attr_init初始化一个属性,后续才能利用这个初始化好的属性值。

在设置堆栈大小上,不得不提到一个重要的属性是线程栈末尾的警戒缓冲区,int
pthread_attr_setguardsize(pthread_attr_t *attr, size_t
guardsize);设置线程的堆栈保护区,第二个参数设置堆栈的保护区,一般默认是4096个字节,一旦线程栈在使用中溢出并到达了这片内存,程序可以捕获系统内核发出的告警信号,然后使用
malloc获取另外的内存,并通过stackaddr改变线程栈的位置,以获得额外的栈空间,这个动态扩展栈空间办法需要手工编程。

线程的结束,我们在启动一个线程,强烈建议不要在外部用pthread_kill,pthread_cancel等函数强行的中端一个线程,这样容易导致很多问题,因为这样的强行取消我们不清楚线程终止的地方会是在哪里,可能会在这之前动态申请了内存,而没有得到释放,更严重的是在刚进入一个加锁代码中,然后被强制退出,那么就会导致死锁现象,所以建议让线程能够自己执行完,让所有的资源能够得到释放,若必须使用外部干扰,那么我们需要在线程里面设置安全的取消点,也就是当线程接收到cancel的时候,不会马上退出,运行到一个相对比较安全的地方,然后再退出本次线程,设置取消点上,我们需要让本次线程能够做到延迟取消 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);在线程设置这个属性,然后设置一个取消点,取消点有很多的线程函数都可以,常用的是pthread_testcancel(),pthread_join()等

第二个,同步

时间: 2024-11-10 07:36:30

linux下的多进程,多线程编程的相关文章

linux下C语言多线程编程实例

学东西,往往实例才是最让人感兴趣的,老是学基础理论,不动手,感觉没有成就感,呵呵.下面先来一个实例.我们通过创建两个线程来实现对一个数的递加.或许这个实例没有实际运用的价值,但是稍微改动一下,我们就可以用到其他地方去拉.下面是我们的代码: /*thread_example.c : c multiple thread programming in linux *author : falcon *E-mail : [email protected] */ #include <pthread.h>

Linux下简单的多线程编程--线程池的实现

/* 写在前面的话: 今天刚“开原”,选择了一篇关于线程池的文件与大家分享,希望能对您学习有所帮助,也希望能与大家共同学习! 选择在这个特殊的时候注册并发文章也是有一些我个人特殊的意义的,看我的id(西游小学生.45)就知道了,哈哈.在这里也很感谢博客园的员工,刚发申请两分钟就同意了. */ 最近由于要写一个类似于QQ的程序,所以想到要用到多线程.既然要用多线程,那何不写一个线程池?于是上网搜了搜多线程的代码,发现大多都不是很完善,或者有些小bug.所以,在这里贴出一个完整的,经过我多重测试的,

Linux程序设计学习笔记----多线程编程基础概念与基本操作

转载请注明出处,http://blog.csdn.net/suool/article/details/38542543,谢谢. 基本概念 线程和进程的对比 用户空间资源对比 每个进程在创建的时候都申请了新的内存空间以存储代码段\数据段\BSS段\堆\栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源. 而每个新创建的线程则仅仅申请了自己的栈,空间,与同进程的其他线程共享该进程的其他数据空间包括代码段\数据段\BSS段\堆以及打开的库,mmap映射的文件与共享的空间,使得

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

转 学习linux下的c/c++编程

http://blog.csdn.net/byxdaz/article/details/3959680 1,先有linux环境搭minGW和cygwin都有点麻烦,最最简单的办法还是装个真正的linux,用虚拟机也好,在网络上的另一台机器也好.这样不仅快,而且你有了真正的环境.2.会C/C++语言(估计你会的)3.入门阶段熟悉gcc命令行,最基本的参数,如,-g,-W,-O,-o,-c 建议看man gcc(很大找想要的)4.编译第一个helloworld程序: 基本命令 gcc hellowo

linux下Eclipse进行C编程时动态链接库的生成和使用

引用 http://linux.chinaitlab.com/soft/864157.html 欢迎进入Linux社区论坛,与200万技术人员互动交流 >>进入 一.创建动态链接库1.创建工程new->project->c++ project选择Shared Library->Empty Project.输入工程名a,点击finish,完成工程的创建. 2.编写代码在windows下封装动态链接库时对要封的函数要用__declspec(dllexport)来标明,在linux

linux下的c语言编程删除文件夹

刚刚在学习开始做新项目的时候,学长布置了一项任务,就是在给定一个目录下,要将这个目录下一个星期之前的目录包括里面的文件全部删除,只保留这一个星期内的.百度了好久的资料,终于完成,记录一下防止忘记.(注:文件夹名称默认为日期格式,如20140716) #include<dirent.h> #include<sys/types.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #i

Linux下C语言多线程,网络通信简单聊天程序

原文:Linux下C语言多线程,网络通信简单聊天程序 功能描述:程序应用多线程技术,可是实现1对N进行网络通信聊天.但至今没想出合适的退出机制,除了用Ctr+C.出于演示目的,这里采用UNIX域协议(文件系统套接字),程序分为客户端和服务端.应用select函数来实现异步的读写操作. 先说一下服务端:首先先创建套接字,然后绑定,接下进入一个无限循环,用accept函数,接受“连接”请求,然后调用创建线程函数,创造新的线程,进入下一个循环.这样每当有一个新的“连接”被接受都会创建一个新的线程,实现