系统相关
首页 > 系统相关> > 线程与进程的优劣对比

线程与进程的优劣对比

作者:互联网

文章目录


前言

线程与进程在liunx系统编程中都是非常重要的一部分,并且两者有很多相似的地方,也有许多不同的地方,今天我们来比较一下两者,并做一下线程编程实战

一、线程与进程的对比

1.两者的优劣对比

UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。  
  进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

"进程——资源分配的最小单位,线程——程序执行的最小单位"

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

2.使用多线程的理由

从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。

使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

二、线程编程实战

1.相关api的介绍

  1. 线程创建
    #include <pthread.h>
    int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
    // 返回:若成功返回0,否则返回错误编号
      当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
      新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

  2. 线程退出
      单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
      1)线程只是从启动例程中返回,返回值是线程的退出码。
      2)线程可以被同一进程中的其他线程取消。
      3)线程调用pthread_exit:
    #include <pthread.h>
    int pthread_exit(void *rval_ptr);
      rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

  3. 线程等待
    #include <pthread.h>
    int pthread_join(pthread_t thread, void **rval_ptr);
    // 返回:若成功返回0,否则返回错误编号

调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
  
  可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
  
  如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
  
4线程ID获取及比较
#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID

2.初级实战代码1

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

void *func1(void *arg)
{
//      static int ret=10;//检验退出函数pthread_exit能否返回整数
        static char *p="wencai love pingping";//检验退出函数pthread_exit能否返回字符串

        printf("t1:%ld thread is creat\n",(unsigned long)pthread_self());//输出进程id号

        printf("t1 param is %d\n",*((int *)arg));

        pthread_exit((void *)p);//主动退出进程
}

void *func2(void *arg)
{

        //static int ret=10;//检验退出函数pthread_exit能否返回整数
        static char *p="wencai love pingping";//检验退出函数pthread_exit能否返回字符串

        printf("t2:%ld thread is creat\n",(unsigned long)pthread_self());//输出进程id号

        printf("t2 param is %d\n",*((int *)arg));

        pthread_exit((void *)p);//主动退出进程

}

int main()
{
        int ret;

        int ret1;

        int param=100;

        char *pret=NULL;

        pthread_t t1;

        pthread_t t2;

        ret=pthread_create(&t1,NULL,func1,(void *)&param);//创建进程

        if(ret==0){
                printf("main:creat t1 success\n");
              }

        ret1=pthread_create(&t2,NULL,func2,(void *)&param);

        if(ret1==0){
                printf("main:creat t2 success\n");
                }

        printf("main:%ld\n",(unsigned long)pthread_self());

        pthread_join(t2,(void **)&pret);//等待进程开始,防止主线程结束,导致子线程提前结束。

        printf("main:t1 quit %s\n",pret);//检验返回的字符串

        printf("main:t2 quit %s\n",pret);


        return 0;
}

看看在liunx平台下的运行效果
在这里插入图片描述
可以看到两个线程都成功创建了,但不能体现线程的存储空间是共享的。因而下面的代码将能体现这个特点。

3.初级实战代码2

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


int data=0;//定义一个全局变量,用于检验线程存储空间的共享

void *func1(void *arg)
{

//      static int ret=10;
        static char *p="wencai love pingping";

        printf("t1:%ld thread is creat\n",(unsigned long)pthread_self());

        printf("t1 param is %d\n",*((int *)arg));

        while(1){//利用死循环让线程打印多个全局变量的值,从运行结果来看,可以知道每次的值都是不连续的,因而证明了线程的存储空间是共享的。
                printf("t1:data=%d\n",data++);
                sleep(1);
                }

}

void *func2(void *arg)
{
        //static int ret=10;
        static char *p="wencai love pingping";

        printf("t2:%ld thread is creat\n",(unsigned long)pthread_self());

        printf("t2 param is %d\n",*((int *)arg));

        while(1){//利用死循环让线程打印多个全局变量的值,从运行结果来看,可以知道每次的值都是不连续的,因而证明了线程的存储空间是共享的。
                printf("t2:data=%d\n",data++);
                sleep(1);
                }

}

int main()
{
        int ret;

        int ret1;

        int param=100;

        pthread_t t1;

        pthread_t t2;

        ret=pthread_create(&t1,NULL,func1,(void *)&param);

        if(ret==0){
                printf("main:creat t1 success\n");
                }

        ret1=pthread_create(&t2,NULL,func2,(void *)&param);

        if(ret1==0){
                printf("main:creat t2 success\n");
                }

        printf("main:%ld\n",(unsigned long)pthread_self());

        while(1){//利用死循环让线程打印多个全局变量的值,从运行结果来看,可以知道每次的值都是不连续的,因而证明了线程的存储空间是共享的。
                printf("main:data=%d\n",data++);
                sleep(1);
                }

        pthread_join(t1,NULL);

        pthread_join(t2,NULL);
        
        return 0;
}

再来看看运行效果
在这里插入图片描述
可以看到两个线程对于全局变量data是共享的,这从而证明了线程存储空间是共享的

总结

对于上面线程相关的demo,我们可以看到,两个线程之间是相互竞争的,我们不知道哪个线程先执行,也无法操控线程运行的先后,因而在后续的博文中,将会引入条件变量和互斥量来实现对线程的控制。

标签:int,优劣,void,printf,线程,pthread,进程,对比
来源: https://blog.csdn.net/weixin_48264057/article/details/111613064