zoukankan      html  css  js  c++  java
  • 多进程编程

     
     
     
    1.多进程
        一个程序的执行活动,就是一个进程,系统为这个进程分配独立的地址空间,资源等等,所以进程事实上就是一个资源的集合体。进程就是为多道编程服务的,通过系统的调度,使得系统可以执行多个进程,使得多个进程看起来都可以同时被系统执行。
     
        多进程编程主要的内容包括进程的控制和进程间的通信。
     
     
     
    1.1 进程的控制
        1.1.1 进程的创建
    1. pid_t fork(void)
    2. fork()被调用一次,却返回两次,可能的返回值:
    3. 1.在父进程中,fork 返回新创建的子进程的的 PID
    4. 2.在子进程中,fork 返回零
    5. 3.如果出现错误, fork 返回一个负值
    6. 子进程被创建之后,会从 fork()之后的代码开始运行,而不会重复运行父进程运行过的代码,
    1.  1 int main(int argc,char*argv)
       2 {
       3     pid_t pid;
       4     pid = fork();// 创建进程之后,父子进程,都是从这边开始去执行代码
       5     if(pid <0)
       6         printf("error
      ");
       7     elseif(pid ==0)
       8         printf("child PID = %d
      ", getpid());
       9     else
      10     printf(" PPID = %d
      ", getppid());
      11     return0;
      12 }    
        fork() 运行的时候,其实会返回两次数值,父进程返回一次,子进程返回一次。程序运行到 fork() 函数的时候,全部的代码会被共享一份出来,变成两份代码一起运行,也就是说代码会被共享。而数据的部分,是从父进程拷贝一份出来给子进程,也就是说数据是拷贝。之后的运行,各个进程都有自己独立的地址空间和资源,就各归各的,以后他们要进行通信的话,就只能是实现进程间的通信了。
        一般来说,因为对于父进程,fork() 返回的是子进程的 pid,而子进程返回的是零,返回值的不同,所以我们一般都是通过 if/...... less 的方式,实现父子进程执行不同的代码,完成不同的功能。
    1. pid_t vfork(void)也是创建子进程,
    区别:
        数据段的区别:
        vfork():子进程与父进程共享父进程的数据段
        fork: 子进程拷贝父进程的数据段
     
        执行数序的区别:
        vfork : 子进程先运行,子进程运行  exec 函数族的时候或者 exit 的时候,才运行父进程
        fork : 父子进程的顺序不确定
     
        vfork 和fork,都创建了进程,但是 vfork 一般的主要目的是 使用  exex 函数族执行其他的程序,实际上,在没有调用 exec 和 exit 子函数之前,子进程与父进程是共享数据段的。vfork 运行,子进程先运行,父进程被挂起,知道子进程调用 exec 或者 exit 。父子进程就不再有限制。
     
        相同:
        两者都是被调用一次,返回两次。子进程的返回值是零,父进程的返回值是子进程的的进程ID,
     
        1.1.2 exec 函数族
        fork 是创建一个新的进程,产生一个新的 PED,但是 exec 则是启动一个新的进程,然后抵换原有的进程,所以进程的 PED 是不会被改变。
    1. 1 int execl(constchar*path,constchar*arg1,,,,)
      2 path :被指定的程序名字,包含完整的路径
      3 arg1 - argn 被指定的程序所需的命令行的参数,以空指针(NULL)结束。
     
    1. 1 int execlp(constchar*path,constchar*arg1,,,,)
      2 path :被执行的程序名,这个是不包含路径的哦,
      3 argv :被指定的命令行参数,还是以空指针,NULL ,结束
     
    1. 1 int execlv(constchar*path,constchar*argv[])
      2 path :被执行的程序名,包含完整的路径
      3 argv :被指定的命令行参数,是包含在数组里面
    1. 1 int system(constchar*string )
      2 调用 fork 产生子进程,由子进程来调用/bin/sh -c string 来执行 string 所代表的命令
     
     
     1.1.3 进程等待
    pid_t wait(int*status )
    阻塞该进程,让它一直等待,知道他的某个子进程退出,
     
     
     
     1 int main(int argc,char**argv)
     2 {
     3     pid_t pc, pr;
     4     // 创建进程
     5     pc = fork();
     6     if(0== pc)
     7     {
     8         printf("这是子进程,进程号是 : %d
    ", getpid());
     9         sleep(10);
    10     }
    11     elseif(pc >0)
    12     {// 保证是子进程运行之后,再运行父进程,
    13         pr = wait(NULL);
    14         printf("这是父进程,进程号是 : %d
    ", getppid());
    15     }
    16     return0;
    17 }    
    只要出现了 wait ,那么进程就会被阻塞,直到子进程退出之后,再运行父进程
     
     
     
     
     
    1.2 进程间通信
        任务的完成不是由一个单一的进程完成的,所以必须进行通信。
     
    通信的方式:
        (1)管道
        (2)信号
        (3)消息队列
        (4)共享内存
        (5)信号量
        (6)套接字
     
    1.2.1 管道
        管道是进程间通信最古老的方式,分为无名管道和有名管道。
        
        A. 无名管道
        用于父子进程间的通信,
    1 创建管道:
    2 int pipe(int filedis[2]);
        管道被创建,会返回来两个文件的描述符。 fileis[0] 用于读管道,fileis[1]用于写管道。无名管道被用于父子进程之间的通信,所以,必须是先创建一个管道,然后在 fork 函数创建进程,这样子进程也就会完全进程父进程所创建的管道,所以必须记住,先创建管道,再创建进程。
     1 #define INPUT 1
     2 #define OUTPUT 0
     3  
     4 void main(){
     5     int file_descriptors[2];
     6     /*定义子进程号 */
     7     pid_t pid;
     8     char buf[256];
     9     int returned_count;
    10     /*创建无名管道*/
    11     pipe(file_descriptors);
    12     /*创建子进程*/
    13     if((pid = fork())==-1){
    14     printf("Error in fork/n");
    15     exit(1);
    16 }
    17 /*执行子进程*/
    18 if(pid ==0){
    19     printf("in the spawned (child) process.../n");
    20     /*子进程向父进程写数据,关闭管道的读端*/
    21     close(file_descriptors[INPUT]);
    22     write(file_descriptors[OUTPUT],"test data", strlen("test data"));
    23     exit(0);
    24 }
    25 else{
    26     /*执行父进程*/
    27     printf("in the spawning (parent) process.../n");
    28     /*父进程从管道读取子进程写的数据,关闭管道的写端*/
    29     close(file_descriptors[OUTPUT]);
    30     returned_count = read(file_descriptors[INPUT], buf,sizeof(buf));
    31     printf("%d bytes of data received from spawned process: %s/n",
    32     returned_count, buf);
    33 }
    34 }
        B. 有名管道
        用于任意两个进程之间的通信
    1 int mkfifo(constchar*pathname,mode_t mode)
    2 pathname : FIFO 文件名,实质上,就是文件名嘛。
    3 mode :属性,
    4 实质上,命名管道,就是一个文件,两个进程之间的通信,实质就是通过一个文件,进行通信
        明明管道,本质上就是一个文件,所以创建了文件之后,就可以使用文件的 read write open 对文件进行操作了。
    read.c:
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
     
    int main()
    {
    char buf[100];
    int fd;
    int by;
     
    // 创建管道
    mkfifo("FIFO", O_CREAT | O_RDONLY);
    // 清零
    memset(buf,0,sizeof(buf));
    // 打开管道,其实就是文件拉,
    fd = open("FIFO", O_RDONLY | O_NONBLOCK);
    if(fd ==-1)
    {
    printf(" failed to open
    ");
    exit(-1);
    }
     
    while(1)
    {// 循环,读取,循环清零
    memset(buf,0,sizeof(buf));
    if((by = read(fd, buf,100))==-1)
    {
    printf("no data
     ");
    }
    printf("read %s 
    ", buf);
    sleep(1);
    }
    return0;
    }
    write.c:
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
     
    int main(int argc,char*argv)
    {
    char buf[100];
    int fd;
    int by;
     
    if(argc <2)
    {
    printf("give moer inf 
    ");
    exit(-1);
    }
     
    // 清零
    memset(buf,0,sizeof(buf));
    // 打开管道,其实就是文件拉,
    fd = open("FIFO", O_RDONLY | O_NONBLOCK);
    if(fd ==-1)
    {
    printf(" failed to open
    ");
    exit(-1);
    }
     
    // 将输入的字符串,拷贝给 buf
    strcpy(bufm argv[1]);
     
    if((by = write(fd, buf,100))==-1)
    {
    printf("error
    ");
    }
    else
    {
    printf("FIFO is %s 
    ", buf);
    }
    return0;
    }
     
     
     
    1.2.2 信号
    信号的机制是 Linux 最为古老的方式,
     
    信号的发送:
    1 int kill(pid_t pid,int signo)
    2 kill 可以给自己,也可以给其他的进程发送信号
    3 pid :指定发送信号的进程号,就把 signo 发送给指定的进程
     
    int raise(int signo)
    raise 只能给进程的自身发送信号,自己给自己发,所以不需要 PID
     
    1 unsignedint alarm(unsignedint seconds)
    2 指定了当经过 seconds 秒之后,就会自己给自己发送一个 SIFALRM 信号
    3  
    4 int pause(void)
    5 使进程等待,一直等到接收到一个信号为止,才不在等待
    6  
    7 信号的处理
    8 接收到信号,要么忽略,要么按照指定的函数执行,要么就按照默认的方式去进行处理
     
     1 void(*signal(int signo,void(*func)(int)))(int)
     2 func :
     3 SIG_IGN :忽略信号
     4 SIG_DFL :按照默认的方式去处理
     5 信号处理的函数名:指定函数,传入的参数,就是信号比如 SIGINT
     6 返回值:还是一个函数的指针,
     7 void func(int sign_no)
     8 {
     9 if(sign_no == SIGINT)
    10 printf("signal is SIGINT
    ");
    11  
    12 if(sign_no == SIGQUIT)
    13 printf("signal is SIGQUIT
    ");
    14  
    15 }
    16 int main(int argc,char** argv)
    17 {
    18 // 注册信号处理的函数
    19 signal(SIGINT, func);
    20 signal(SIGQUIT, func);
    21 // 注册好之后,会一直等待,知道有参数进来
    22 pause();
    23 return0;
    24 }
     
    kill - s +信号+ PID
     
    1.2.3 消息队列
    和滚到很类似,这个是快被淘汰的方式。
     
    1.2.4 共享内存
    共享内存是运行在同一台机器上最快的通信的方式,因为数据不需要不同的进程间的复制。本质上,是一个进程创建一个共享的内存,然后其他的进程则对这块内存进行读写。
    A. 创建内存
    int shmget(key_t key,int size,int shmflg)
    key:内存创建的标志,0/ IPC_PRIVATE,IPC_PRIVATE,表示创建新的共享的内存,
    size :创建的大小,以字节为单位
    成功创建的话,返回内存的标识符,错误就为-1
        内存的创建和 malloc 非常的类似
     
    B. 映射内存
    int shmat(int shmid,char*shmaddr,int flag )
    shmid :是shmget 函数返回的光纤内存的标识符
    flag :一般是零
    shmaddr :获取值,获得共享内存的地址,一般为零,让系统分配
    成功的话,返回地址,失败的话,返回值为-1
    C.解除内存
    char *shmdt(char*shmaddr)
    char*c_addr,*p_addr;
    int shmid;
    // 分配内存
    shmid = shmget(IPC_PRIVATE,1024, S_IRUSR | S_IWUSR);
     
    if(fork())
    {// 获得映射的地址
    p_addr = shmat(shmid,0,0);
    memset(p_addr,0,1024);
    strncpy(p_addr, argv[1],1024);
    // 等待,子进程结束
    wait(NULL);
    exit(0);
    }
    else
    {// 保证父进程先执行
    sleep(2);
    // 获得映射的地址
    c_addr = shmat(shmid,0,0)
    printf(" get %s 
    ", c_addr);
    }
     
     
    1.2.5 套接字
    这个部分,是使用 socket 编程的套接字。
     
     
    1.2.6 信号量
    5.信号量
        信号量,又叫信号灯,实质上就是一个计数器,与原子的操作类似。用于为多个线程提供共享数据对象的访问。保证了共享资源对象,会被有限调用。
        执行的操作:
        (1)设置信号量,这个信号量的值自己设定,值为最多可以接收访问的最大值,非负值
        (2)当一个线程访问的时候,只要信号量的值不为零,那么就可以被访问,此时,信号量的值就减一,没来一次访问就减一;当信号量的值为零,那么进程就会进入休眠;
        (3)当一个线程访问完毕之后,就要释放信号量,也就是信号量的值就加一,这样以前被休眠的进程,就会被唤醒,
        信号量的操作:
        所以信号量在创建的时候,就必须设置一个非零的初始值,表示同时有几个线程访问该信号量保护起来的共享的资源,当初始值为一的时候,就变成互斥锁了,关于互斥锁的资料,后面补齐。,互斥锁的话,每次只能有一个线程访问资源。
        信号量的API(内核):
    1)信号量的初始化
    DEFINE_SEMAPHORE(semaphore_lock)// 一步完成信号量的定义和初始化,此时的信号量设置为1,也就是为互斥锁
    // 也可以,分开来,不过比较麻烦,不过是一样的作用
    staticstruct semaphore sem;// 信号量的定义
    sema_init(struct semaphore * sem,int val);// 信号量的初始化
     
    2)获得信号量
    void down(struct semaphore *sem)// 获得信号量,其实做减一,当为零的时候,进程就好休眠
    int down_interruptible(struct semaphore * sem)
    int down_trylock(struct semaphore * sem)// 三种获得信号量的区别,这里不做详解
     
    3)释放信号量
    void up(struct semaphore *sem)// 其实就是做加一的动作,将信号量的值加一
     
  • 相关阅读:
    Dilworth定理,链还是反链?
    pku 3259Wormholes
    hdu 2612 Find a way
    hdu 2614
    hdu 2544 最短路
    hdu 2553 N皇后问题
    hdu2717 Catch That Cow
    hdu 1874 畅通工程续
    jquery学习必备代码和技巧
    HTML5 WebApp part4:使用 Web Workers 来加速您的移动 Web 应用程序(上)
  • 原文地址:https://www.cnblogs.com/qxj511/p/4917710.html
Copyright © 2011-2022 走看看