zoukankan      html  css  js  c++  java
  • 操作系统多进程编程、多线程编程

    多线程:

    一个多线程的简单实现:

    https://blog.csdn.net/sujianwei1123/article/details/76183682

    fork函数需要unistd.h的头文件,unistd.h头文件的接口通常都是大量针对系统调用的封装,是对类 Unix 系统。

    getpid函数是获得进程号

    sleep函数:执行挂起一段时间,也就是等待一段时间在继续执行。秒级别。

    exit函数讲解:https://blog.csdn.net/love_gaohz/article/details/46667007 当进程执行到exit()或_exit()函数时,进程会无条件的停止剩下的所有操作,清除各种数据结构,并终止本进程的运行。每个exit函数也只关闭相对应的进程。

    在操作系统的基本概念中进程是程序的一次执行,且是拥有资源的最小单位和调度单位(在引入线程的操作系统中,线程是最小的调度单位)。在Linux系统中创建进程有两种方式:一是由操作系统创建,二是由父进程创建进程(通常为子进程)。系统调用函数fork()是创建一个新进程的唯一方式,当然vfork()也可以创建进程,但是实际上其还是调用了fork()函数。fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值,下面是fork()函数的声明:

    当程序调用fork()函数并返回成功之后,程序就将变成两个进程,调用fork()者为父进程,后来生成者为子进程。这两个进程将执行相同的程序文本,但却各自拥有不同的栈段、数据段以及堆栈拷贝。子进程的栈、数据以及栈段开始时是父进程内存相应各部分的完全拷贝,因此它们互不影响。从性能方面考虑,父进程到子进程的数据拷贝并不是创建时就拷贝了的,而是采用了写时拷贝(copy-on -write)技术来处理。调用fork()之后,父进程与子进程的执行顺序是我们无法确定的(即调度进程使用CPU),意识到这一点极为重要,因为在一些设计不好的程序中会导致资源竞争,从而出现不可预知的问题。

    注意:for循环生成了5个pid,再加上父进程就是6个进程。刚开始以为for循环进程的生成,后面的pid会覆盖掉前面的pid,以为就生成了一个子进程,事实是生成5个子进程,不会覆盖

       fork返回值为0,子进程;不为0,父进程

    个人理解其实就是通过pid的编号来实现控制不同的进程的运行和终止。

    #include<stdio.h>
    #include<unistd.h>
    #include <sys/types.h>
    #include<vector>
    #include <iostream>
    
    #include <stdlib.h>
    
    using namespace std;
    int main()
    {
        string sMatch;
        pid_t pid;
        vector<string> provList;
        provList.push_back("100");
        provList.push_back("200");
        provList.push_back("300");
        provList.push_back("400");
        provList.push_back("500");
        cout<<"main process,id="<<getpid()<<endl;
        //循环处理"100,200,300,400,500"
        for (vector<string>::iterator it = provList.begin(); it != provList.end(); ++it)
        {
            sMatch=*it;
            pid = fork();
            //子进程退出循环,不再创建子进程,全部由主进程创建子进程,这里是关键所在
            if(pid==0||pid==-1)
            {
                break;
            }
        }
        if(pid==-1)
        {
            cout<<"fail to fork!"<<endl;
            exit(1);
        }
        else if(pid==0)
        {
            //这里写子进程处理逻辑
            cout<<"this is children process,id="<<getpid()<<",start to process "<<sMatch<<endl;
            sleep(10);
            exit(0);
        }
        else
        {
            //这里主进程处理逻辑
            cout<<"this is main process,id="<<getpid()<<",end to process "<<sMatch<<endl;
            exit(0);
        }
        return 0;
    }

    Linux下一个进程在内存里有三部分的数据,就是”代码段”、”堆栈段”和”数据段”。接触过汇编语言的人了解,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。

      “代码段”,顾名思义,就是存放了程序代码的数据,如果机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。”堆栈段”存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

      有两个基本的操作用于创建和修改进程:函数fork()用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝,利用了父进程的代码段、堆栈段、数据段,当父子进程中对共有的数据段进行重新设值或调用不同方法时,才会导致数据段及堆栈段的不同;函数族exec()用来启动另外的进程以取代当前运行的进程,除了PID仍是原来的值外,代码段、堆栈段、数据段已经完全被改写了。

    https://blog.csdn.net/sodino/article/details/45146001

    http://www.doc88.com/p-9681830324447.html

    多线程编程:

    使用pthread.h的库函数

    1.一个简单例子:

      pthread_create:创建一个新的线程的函数,一旦创建就会执行这个函数

      pthread_exit:用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用

      注意:1.线程的运行函数在函数名前加了*号,因为pthread_create函数的第三个参数是线程运行函数起始地址

          2.pthread_create返回值,创建线程成功时,函数返回 0;若返回值不为0则说明创建线程失败

         3.使用 -lpthread 库编译

    #include <iostream>
    #include <pthread.h>
     
    using namespace std;
    #define NUM_THREADS 5
     
    // 线程的运行函数
    void* say_hello(void* args)
    {
        cout << "Hello Runoob!" << endl;
        return 0;
    }
     
    int main()
    {
        // 定义线程的 id 变量,多个变量使用数组
        pthread_t tids[NUM_THREADS];
        for(int i = 0; i < NUM_THREADS; ++i)
        {
            //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
            int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
            if (ret != 0)
            {
               cout << "pthread_create error: error_code=" << ret << endl;
            }
        }
        //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
        pthread_exit(NULL);
    }

    每次的输出结果不一定一样:

    2.给线程调用的函数传参数:

    pthread_create函数的第四个参数就是可以传递给运行函数的参数

    #include <iostream>
    #include <cstdlib>
    #include <pthread.h>
     
    using namespace std;
     
    #define NUM_THREADS     5
     
    void *PrintHello(void *threadid)
    {  
       // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
       int tid = *((int*)threadid);
       cout << "Hello Runoob! 线程 ID, " << tid << endl;
       pthread_exit(NULL);
    }
     
    int main ()
    {
       pthread_t threads[NUM_THREADS];
       int indexes[NUM_THREADS];// 用数组来保存i的值
       int rc;
       int i;
       for( i=0; i < NUM_THREADS; i++ ){      
          cout << "main() : 创建线程, " << i << endl;
          indexes[i] = i; //先保存i的值
          // 传入的时候必须强制转换为void* 类型,即无类型指针        
          rc = pthread_create(&threads[i], NULL, 
                              PrintHello, (void *)&(indexes[i]));
          if (rc){
             cout << "Error:无法创建线程," << rc << endl;
             exit(-1);
          }
       }
       pthread_exit(NULL);
    }

    输出的结果:

    两次的结果不一样,有“创建函数”几个字的行应该都是在运行主线程

    3.线程调用的函数在一个类中,那必须将该函数声明为静态函数

    因为静态成员函数属于静态全局区,线程可以共享这个区域,故可以各自调用

    #include <iostream>  
    #include <pthread.h>  
      
    using namespace std;  
      
    #define NUM_THREADS 5  
      
    class Hello  
    {  
    public:  
        static void* say_hello( void* args )  
        {  
            cout << "hello..." << endl;  
        }  
    };  
      
    int main()  
    {  
        pthread_t tids[NUM_THREADS];  
        forint i = 0; i < NUM_THREADS; ++i )  
        {  
            int ret = pthread_create( &tids[i], NULL, Hello::say_hello, NULL );  
            if( ret != 0 )  
            {  
                cout << "pthread_create error:error_code" << ret << endl;  
            }  
        }  
        pthread_exit( NULL );  
    }  

    4.

     线程同步:一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才能执行

       线程互斥:一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,解锁

       线程互斥是一种特殊的线程同步。

     Linux中 四种进程或线程同步互斥的控制方法:

      1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 
      2、互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计的。 
      3、信号量:为控制一个具有有限数量用户资源而设计。 
      4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

     Linux下线程同步最常用的三种方法就是互斥锁、条件变量及信号量,互斥锁通过锁机制来实现线程间的同步锁机制是同一时刻只允许一个线程执行一个关键部分的代码条件变量是用来等待而不是用来上锁的,条用来自动阻塞一个线程,直到某特殊情况发生为止

     互斥锁的一个实例https://blog.csdn.net/Return_nellen/article/details/79916519:

    PTHREAD_MUTEX_INITIALIZER:锁的初始化,pthread_mutex_lock:加锁,pthread_mutex_unlock:释放锁

    #include <iostream>
    #include <pthread.h>
    #include <stdio.h>
    #include <semaphore.h>
    #include <stdlib.h>
    #include <string.h>
    using namespace std;
    pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
    
    int tf[5];
    
    void* print(void* i)
    {
        pthread_mutex_lock(&mut);
        for(int j=0;j<3;j++)
            // cout << i << " " << j << endl;
            cout << j << endl;
        pthread_mutex_unlock(&mut);
    }
    
    int main()
    {
        pthread_t td[3];
        for(int i=0;i<3;i++)
            tf[i] = i;
        for(int i=0;i<3;i++)
            pthread_create(&td[i],NULL,print,(void *)&tf[i]);
        for(int i=0;i<3;i++)
            pthread_join(td[i],NULL);
        pthread_mutex_destroy(&mut);
    }

    运行结果:

    如果不用互斥锁,也就是在运行函数那将锁的打开和关闭注释掉,运行结果如下:

    可以看到加锁是一个线程一个线程的运行,输出的结果是有序的,不加锁就是乱序的

    条件变量的实例:https://www.cnblogs.com/xudong-bupt/p/6707070.html

    pthread_cond_wait:线程阻塞在条件变量

    pthread_cond_signal:线程被唤醒

    pthread的pthread_join函数https://blog.csdn.net/dinghqalex/article/details/42921931:

      使用方式:创建线程之后直接调用pthread_join方法就行了

      在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到pthread_join()方法了。即pthread_join()的作用可以这样理解:主线程会阻塞等待子线程的终止。也就是在子线程调用了pthread_join()方法后面的代码,只有等到子线程结束了才能执行

    读写锁和互斥锁区别:

      读写锁特点:

        1)多个读者可以同时进行读
        2)写者必须互斥只允许一个写者写,也不能读者写者同时进行
        3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

      互斥锁特点:

          一次只能一个线程拥有互斥锁,其他线程只有等待

    c++11有STL封装的thread类,它的前身据说是boost::thread

    多进程多线程选择:https://blog.csdn.net/RUN32875094/article/details/79515384

    1)需要频繁创建销毁的优先用线程

    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

    2)需要进行大量计算的优先使用线程

    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

    这种原则最常见的是图像处理、算法处理。

    3)强相关的处理用线程,弱相关的处理用进程

    什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

    一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

    当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

    4)可能要扩展到多机分布的用进程,多核分布的用线程

    原因请看上面对比。

     

    https://zhuanlan.zhihu.com/p/37029560 python多线程多进程

  • 相关阅读:
    Spring Aop
    Java 内存分配
    wrapper class (Integer 为例)
    asp.net mvc 中"未找到路径“/favicon.ico”的控制器或该控制器未实现 IController。"
    .Net反射机制
    设计模式系列一创建型之(抽象工厂模式)
    设计模式系列一创建型之(单件模式)
    设计模式系列二结构型之(装饰者模式)
    设计模式系列二结构型之(策略者模式)
    设计模式系列一创建型模式之(简单工厂VS工厂方法)
  • 原文地址:https://www.cnblogs.com/ymjyqsx/p/9746390.html
Copyright © 2011-2022 走看看