多进程/多线程

这里写图片描述
进程是表示资源分配的基本单位,又是调度运行的基本单位。
线程是进程中执行运算的最小单位,亦即执行CPU调度的基本单位。

一,适用场景:
1>需要大量的计算
如果你在实际的应用中需要大量的计算,那么你可以优先使用线程。由于大量的计算会耗费很多的CPU并且切换回很频繁。而我们上文中也有说到,线程的切换简单而且CPU的利用率高。
2>需要频繁创建销毁
如果你实际应用中需要大量的计算,那你可以优先选择使用线程。因为线程的创建销毁比进程的都要简单方便。就比如常见的web服务器,来一个连接就建立一个进程,然后断了就进行销毁。但是由于进程的创建和销毁很麻烦,所以一般都会选用线程。
3>强相关、弱相关
我们先看一个例子,一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。
而一般来说强相关的处理使用线程,弱相关的处理使用进程。
4>多机、多核分布
线程使用于多核分布式,所以多机分布使用进程,多核分布使用线程。

二,代码

多进程代码:
#include <stdlib.h> 
#include <sys/types.h> 
#include <unistd.h> 

int main() 
{ 
  pid_t child_pid; 

  /* 创建一个子进程 */ 
  child_pid = fork(); 
  if(child_pid == 0) 
  { 
    printf("child pid\n"); 
    exit(0); 
  } 
  else
  { 
    printf("father pid\n"); 
    sleep(60); 
  } 

  return 0; 
} 

进程相关问题:
1>进程间通信:
1)管道:
管道分为有名管道和无名管道
无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。
有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

无名管道:优点:简单方便;缺点:1)局限于单向通信2)只能创建在它的进程以及其有亲缘关系的进程之间;3)缓冲区有限;
有名管道:优点:可以实现任意关系的进程间的通信;缺点:1)长期存于系统中,使用不当容易出错;2)缓冲区有限
2)信号量:
信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.
优点:可以同步进程;缺点:信号量有限

3)信号:
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生.

4)消息队列:
消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序.

优点:可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便;缺点:信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合
5)共享内存:
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信.

优点:无须复制,快捷,信息量大;缺点:1)通信是通过将共无法实现享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,因此进程间的读写操作的同步问题;2)利用内存缓冲区直接交换信息,内存的实体存在于计算机中,只能同一个计算机系统中的诸多进程共享,不方便网络通信

6)套接字:可用于不同及其间的进程通信
优点:1)传输数据为字节级,传输数据可自定义,数据量小效率高;2)传输数据时间短,性能高;3) 适合于客户端和服务器端之间信息实时交互;4) 可以加密,数据安全性强
缺点:1) 需对传输的数据进行解析,转化成应用级的数据。
2>僵死进程:
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

僵尸进程解决办法:
1>通过信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
2>原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。即:fork()两次。

多线程 生产者-消费者模式:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

//初始化互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond =PTHREAD_COND_INITIALIZER;

typedef struct listNode Node;
struct listNode
{
    int num;
    struct listNode * pNext;
};

Node *phead=NULL;//define the head point;

void * producter(void * arg)
{
    for(;;)
    {
        //只产生一个节点,即停止生产
        while(phead==NULL)
        {
            //产生一个新的节点
            Node *tmpNode=(Node*)malloc(sizeof(Node));
            tmpNode->num=rand()%1000+1;
            pthread_mutex_lock(&mutex);
            //把节点加入到链表
            tmpNode->pNext=phead;//头插法,即新节点的指针域指向链表的头指针
            phead=tmpNode;//新节点的指针变为链表的头指针。
            cout<<"producing the num = "<<tmpNode->num<<endl;
            pthread_mutex_unlock(&mutex);
            //添加结束,发送信号
            pthread_cond_signal(&cond);
        }
        //  sleep(rand()%1);//方便观察
    }
    return NULL;
}

void * consumer(void * arg)
{
    for (;;)
    {
        pthread_mutex_lock(&mutex);
        //判断链表是否为空,如果为空,则调用条件变量函数,阻塞线程
        while (phead==NULL)
        {
            //阻塞线程,释放锁,收到signal后,释放线程,获得锁
            pthread_cond_wait(&cond,&mutex);
        }
        //如果不为空,则用头删法删除链表的头节点
        //定义一个临时节点保存被删除的头部节点
        Node *tmpNode=phead;
        phead=phead->pNext;//头节点的指针域存的地址作为新的头节点
        cout<<"---------------consumer--------"<<tmpNode->num<<endl;
        free(tmpNode);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main(void)
{
    //定义并创建生产者和消费者线程
    pthread_t ptid,ctid;
    pthread_create(&ptid,NULL,producter,NULL);
    pthread_create(&ctid,NULL,consumer,NULL);

    //等待线程结束
    pthread_join(ptid,NULL);
    pthread_join(ctid,NULL);

    return 0;

}

线程相关问题:
1.线程之间的通信方式?
# 锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
2.多线程的同步和互斥:
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值