2019-04-05 18:49:30 weixin_43784003 阅读数 59

实验二:线程的创建

  本实验由重庆大学大数据与软件学院洪明坚老师指导,使用C语言编写。本文在洪明坚老师讲授内容基础上加以个人总结,课件与源码均来自洪明坚老师。欢迎探讨与指正。

1.实验内容

  随机生成N组非负整数列表,然后创建N个线程,分别用N种不同的排序算法对列表进行排序。

  进入图形模式,沿垂直方向把屏幕分成N个区域,每个排序线程用一个区域,动态显示排序过程。

注:本实验不用自己编写系统调用函数,源码中已经写好,相关函数接口说明见本文最后。

2.实验步骤

Step1:

(1)在userapp/main.c中编写排序函数,我用了四种排序算法(冒泡、选择和、插入和希尔排序),每个线程使用一种。

①冒泡排序

//冒泡排序线程函数
void tsk_bubbleSort(void *p)
{  
     printf("This is bubbleSort_task foo with tid=%d\r\n",task_getid()); 
     int i,j,temp;
     int *arr = (int *)p;
     int y=  g_graphic_dev.YResolution/step

     for(i=0;i<y;i++)
{    
line(0, step*i, arr[i], step*i, RGB(255, 0, 0));
}        

//冒泡排序
for(i=0;i<y-1;i++)
{
   for(j=0;j<y-1-i;j++)
  {
    if(arr[j] > arr[j+1])//如果大于,则交换
            {
                temp = arr[j];
                line(0,step*j, arr[j],step*j, RGB(0, 0, 0));//清除原来的线
                arr[j] = arr[j+ 1];
                line(0,step*j, arr[j],step*j,RGB(255,0,0));//重新画线

                line(0, step*(j + 1), arr[j + 1],step*(j + 1), RGB(0, 0, 0));//清除原来的线
                arr[j+ 1] = temp;
                line(0,step*(j + 1), arr[j + 1],step*(j + 1), RGB(255, 0, 0));//重新画线
             }
   }
    nanosleep((const struct timespec[]){{0,10000000L}}, NULL);//睡眠1s
}
     task_exit(0);
}

②插入排序

//插入排序线程函数
void tsk_insertSort(void *p)
{  
     printf("This is insertSort_task foo with tid=%d\r\n",task_getid()); 
     int i,j,temp;    
     int*arr=(int*)p;    
     int x = g_graphic_dev.XResolution/4;
     int y=  g_graphic_dev.YResolution/step;

   //画排序前数组
for(i=0;i<y;i++)
{
    line(x, step * i,x+ arr[i],step* i, RGB(0, 255, 0));
}
//插入排序
for (i = 1; i <y; i++)//从第二个元素开始
{     
temp = arr[i];
    for (j = i - 1; j >= 0 && arr[j] > temp;j--)
    {
        line(x,step * (j + 1), x+arr[j + 1], step * (j + 1), RGB(0, 0, 0));//清除原来的线
        arr[j + 1] = arr[j];
        line(x,step * (j + 1), x+arr[j + 1], step * (j + 1), RGB(0,255, 0));//重新画线
    }

    line(x, step * (j + 1), x+arr[j + 1],step * (j + 1), RGB(0, 0, 0));//清除原来的线
    arr[j + 1] = temp;
    line(x, step* (j + 1), x+arr[j + 1], step * (j + 1), RGB(0, 255, 0));//重新画线
    nanosleep((const struct timespec[]){{0, 10000000L}}, NULL);
}
     task_exit(0);
}

③选择排序

//选择排序线程函数
void tsk_selectSort(void *p)
{  
     printf("This is selectSort_task foo with tid=%d\r\n",task_getid()); 
     int i,j,lowindex,temp;
     int*arr=(int*)p;
     int x = g_graphic_dev.XResolution / 4;
     int y=  g_graphic_dev.YResolution/step;

for(i=0;i<y;i++)
{
    line(2*x, step* i,2*x+arr[i], step* i, RGB(0, 0, 255));
}
//选择排序

for (i = 0; i < y - 1; i++)
{
    lowindex
= i;
    for (j = y - 1; j > i; j--)
        if (arr[j] < arr[lowindex])
            lowindex= j;
    if (lowindex != i)
{
        temp= arr[i];
        line(2 * x,step * i, 2* x + arr[i], step* i, RGB(0, 0, 0));//清除原来的线
        arr[i] = arr[lowindex];
        line(2* x, step* i, 2 * x + arr[i], step* i, RGB(0, 0, 255));//重新画线

        line(2* x, step*lowindex, 2 * x + arr[lowindex], step* lowindex, RGB(0, 0, 0));//清除原来的线
        arr[lowindex] =temp;
        line(2 * x, step * lowindex,2 * x + arr[lowindex], step* lowindex, RGB(0, 0, 255));//重新画线
    }

    nanosleep((const struct timespec[]){{0,10000000L}}, NULL);
}

task_exit(0);
}

④shell排序

    //shell排序线程函数
void tsk_shellSort(void *p)
{
        void shellSort(int *A, int n);
        printf("This is shellSort_task foo with tid=%d\r\n", task_getid());
        int i;
        int*arr = (int*)p;
        int x = g_graphic_dev.XResolution / 4;
        int y=  g_graphic_dev.YResolution / step;

        for (i = 0; i <y; i++)
        {
            line(3*x, step* i, 3*x + arr[i],step* i, RGB(255, 255, 255));            
        }    
        shellSort(arr,y);
        task_exit(0);
    }

void inssort(int* Arr,int k, int n, int incr)
{
    int i, j,temp;
    int x = g_graphic_dev.XResolution / 4; 
    for ( i = incr; i< n; i += incr)
    {
        for ( j = i; (j>= incr) && (Arr[j]<Arr[j - incr]); j-= incr)
        {
            temp= Arr[j];
            line(3* x, step*(j+k),3 * x + Arr[j], step*(j+k), RGB(0, 0, 0));//清除原来的线
            Arr[j] = Arr[j - incr];
            line(3* x, step* (j +k), 3 * x + Arr[j], step*(j+k), RGB(255, 255, 255));//重新画线
            
           line(3 * x, step* (j+k- incr), 3 * x + Arr[j - incr],step*(j+k - incr), RGB(0, 0, 0));//清除原来的线
            Arr[j - incr] =temp;
           line(3 * x, step* (j+k- incr), 3 * x + Arr[j - incr],step* (j+k - incr), RGB(255, 255, 255));//重新画线         
        }        
    }
}
void shellSort(int *Arr, int n)

{

    int i, j;

    void inssort(int *Arr, int k,int n, int incr);

    for (i = n / 2; i > 2; i /= 2)

        for (j = 0; j < i; j++)

    {

        inssort(&Arr[j],j, n - j,
i);

    nanosleep((const struct timespec[]){{0, 10000000L}}, NULL);}

        inssort(Arr,0, n,1);

}

注:
①这里的line函数用于画线,参数含义为:线两端横纵坐标和颜色RGB值。
②线的长度与数组中的整数大小对应,线越长表示值越大。注意每次画线之前要先擦除原来的线。
③若排序过程太快,可用nanosleep函数使线程睡眠,10000000L表示一秒钟。

另外,附打点方法:
pic1

Step2:在main函数中创建排序线程

(1)进入图形模式

    init_graphic(0x143);//进入图形页面:mode=0x143

(2)获取屏幕分辨率

 int x=g_graphic_dev.XResolution/4;//水平分辨率
 int y=g_graphic_dev.YResolution/step;//垂直分辨率

主要用于绘制排序过程,分割屏幕。本例中,随机数组的范围、长度以及画线都是根据屏幕分辨率来设定的。

(3)申请数组空间并随机赋值

srand(time(NULL));//设置随机数种子
//申请数组空间
int* arr_bubble=(int*)malloc(x*sizeof(int));
int* arr_insert=(int*)malloc(x*sizeof(int));
int* arr_select=(int*)malloc(x*sizeof(int));
int* arr_shell = (int*)malloc(x * sizeof(int));

int i;
for (i=0;i<y;i++)
{
     arr_bubble[i]=rand()%x;
     arr_insert[i]=rand()%x;
     arr_select[i]=rand()%x;  
     arr_shell[i] =rand()%x;
}

注:随机数需要用srand先播种,后使用rand生成。

(4)申请线程空间

//申请线程栈
unsigned int  stack_size = 1024*1024;   
unsigned char* stack_bubble=(unsigned char *)malloc(stack_size);   
unsigned char* stack_insert=(unsigned char *)malloc(stack_size);    
unsigned char* stack_select=(unsigned char *)malloc(stack_size);  
unsigned char* stack_shell=  (unsigned char *)malloc(stack_size);

(5)创建线程

task_create(stack_bubble+stack_size,&tsk_bubbleSort(void*)arr_bubble);
task_create(stack_insert+stack_size,&tsk_insertSort,(void*)arr_insert); 
task_create(stack_select+stack_size,&tsk_selectSort(void*)arr_select); 
task_create(stack_shell+stack_size, &tsk_shellSort, (void*)arr_shell);

注:
①线程函数只有一个void指针,可以将需要传入的参数定义成结构体,再进行强制类型转换。(结构体指针一定要先分配空间!)
②task_create函数的返回值是线程id,本例中并不需要。

(6)退出图形模式

    while(1)
        ;
    exit_graphic();//退出图形模式
    task_exit(0);//退出线程
}

实现效果:

在这里插入图片描述

3.问题总结

(随时更新)

附:系统调用接口说明

在这里插入图片描述

main.c完整代码下载:https://download.csdn.net/download/weixin_43784003/11090897

2017-05-14 18:46:35 Sunird 阅读数 414

初学操作系统对于线程的知识了解是很重要的,因此我们借用对pthread库函数的一些小运用来加深对多线程的认识。
注:本实验在linux系统下进行。

“pthread.h”头文件无法在windows平台下直接引用,需要在编译器编译过程中自行添加,笔者测试用的是pthreads-w32-2-4-0-release.exe,有兴趣的读者可自行研究这里不作扩展。


涉及到的函数有:

pthread_create //用于创建一个新的线程

//pthread_create函数原型:
#include <pthread.h>
 int pthread_create(pthread_t *restrict tidp,
               const pthread_attr_t *restrict attr,
               void *(*start_rtn)(void),
               void *restrict arg);

 Returns: 0 if OK, error number on failure
  • pthread_t *restrict tidp ,tidp是指向一个线程ID的指针。
  • const pthread_attr_t *restrict attr:线程属性设置。
  • void *(*start_rtn)(void),第三个参数是一个指向函数指针,即线程运行函数的起始地址。新创建的线程从该函数的起始地址开始运行。
  • void *restrict arg:给执行函数传递参数参数
    返回值:
    若成功则返回0,否则返回出错编号。

由 restrict修饰的指针是最初唯一对指针所指向的对象进行存取的方法,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改,保证了代码的安全性。

pthread_jion//阻塞,等待线程结束

//pthread_jion函数原型
#include <pthread.h>
int pthread_join(pthread_t thread, 
              void **retval); 
Returns: 0 if OK, error number on failure

-pthread_t thread:线程ID。
-void **retval为一个用户定义的指针,它可以用来存储被等待线程的返回值。
返回值:
若成功则返回0,否则返回出错编号。

当我们需要所有线程都结束之后再对他们的结果进行统一操作时,就需要在主函数里加入阻塞函数,等待所有线程都结束后在进行后续操作。


实例:随机生成10万个浮点数,创建4个线程分别实现2.5万个浮点数之和最后完成10万个浮点数之和。比较多线程和单线程的计算时间。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <memory.h>
#include <sys/time.h>

#define Max_Set 100000
float ssum[Max_Set];
pthread_t thread[4];
float suma=0.0;
float sumb=0.0;
float sumc=0.0;
float sumd=0.0;
float sum=0.0;

void fullssum(float a[],int length)
{
    for(int i=0;i<length;i++)
    ssum[i]=rand()/(float)(RAND_MAX/10);

}


void *thread_a(void *in)
{
    printf("i am thread_a\n");
    for(int i=0;i<Max_Set/4;i++)
    {
        suma+=ssum[i];
    }
    printf("suma=====%f\n",suma);
    pthread_exit((void *)0);
}

void *thread_b(void *in)
{
    printf("i am thread_b\n");
    for(int i=Max_Set/4;i<Max_Set/2;i++)
        {
                sumb+=ssum[i];
        }
        printf("sumb=====%f\n",sumb);

    pthread_exit((void *)0);
}

void *thread_c(void *in)
{
        printf("i am thread_c\n");
        for(int i=Max_Set/2;i<Max_Set*3/4;i++)
        {
                sumc+=ssum[i];
        }
        printf("sumc=====%f\n",sumc);

        pthread_exit((void *)0);
}

void *thread_d(void *in)
{
        printf("i am thread_d\n");
        for(int i=Max_Set*3/4;i<Max_Set;i++)
        {
                sumd+=ssum[i];
        }
        printf("sumd=====%f\n",sumd);
        pthread_exit((void *)0);
}

int main()
{
    int temp;
    struct timeval start, end1,end2;

    //用随机数填满数组
    fullssum(ssum,Max_Set);
    //初始化线程ID
    memset(&thread ,0 ,sizeof(thread));
    //获取当前时间
    gettimeofday(&start,NULL);
    if((temp=pthread_create(&thread[0],NULL,thread_a,NULL))!=0)
    printf("error in thread_a\n");
    else
    printf("thread_a creat success!\n");
    if((temp=pthread_create(&thread[1],NULL,thread_b,NULL))!=0)
        printf("error in thread_b\n");
    else
        printf("thread_b creat success!\n");
     if((temp=pthread_create(&thread[2],NULL,thread_c,NULL))!=0)
        printf("error in thread_c\n");
     else
        printf("thread_c creat success!\n");
     if((temp=pthread_create(&thread[3],NULL,thread_d,NULL))!=0)
        printf("error in thread_d\n");
     else
        printf("thread_d creat success!\n");
    for(int i=0;i<4;i++)
    {
        if(thread[0]!=0)
        {   
        pthread_join(thread[0],NULL);
        printf("thread%d come to an end\n",i+1);
        }
    }
    //等待所有线程结束计算和
    printf("RESULT1=======%f\n",suma+sumb+sumc+sumd);
    //获取当前时间
    gettimeofday(&end1,NULL);
    int time1=1000000*(end1.tv_sec-start.tv_sec)+end1.tv_usec-start.tv_usec;
    //输出多线程计算用时
    printf("time1======%d\n",time1);
    for(int j=0;j<Max_Set;j++)
    {
        sum+=ssum[j];
    }
    printf("RESULT2=======%f\n",sum);
    gettimeofday(&end2,NULL);
    int time2=1000000*(end2.tv_sec-end1.tv_sec)+end2.tv_usec-end1.tv_usec;
    //输出单线程计算用时
    printf("time2======%d\n",time2);
}

使用gcc编译pthread库程序需要加 -lpthread
例如:gcc pthread.c -o thread -lpthread

2017-05-27 22:25:42 weiyongle1996 阅读数 442

一、线程的概念

      线程可以理解为小型、轻型的进程,它是包含在进程中的,线程和进程的具体区别如下:

调度:一个进程可以有多个线程。线程作为CPU调度和分派的基本单位,进程则作为资源分配的基本单位。同一进程中的线程切换不会引起进程切换,从而避免昂贵的系统调用,但是从一个进程的线程切换到另一个进程的线程时,依然会引起进程切换。

切换时的系统开销:由于在创建或者撤销进程时,系统都要为之分配或回收资源,因此,操作系统所付出的开销将显著地大于创建或者撤消线程的开销。在进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置,而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。同时,由于同一个进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现也变得比较容易。

占用的资源:进程是拥有系统资源的一个独立的单位,它可以拥有自己的资源。线程自己一般不拥有资源(有一点必不可少的资源,包括程序计数器,一组寄存器和栈等),但它可以访问其隶属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备等)。

单线程进程与多线程进程区别如下:

多线程的优点

  1. 响应度高(Responsiveness):即使其部分阻塞或执行较冗长操作,该程序仍能继续执行,从而增加了对用户的相应程度。

  2. 资源共享(Resource Sharing):线程默认共享它们所属进程的内存和资源。代码和数据共享的优点是它允许一个应用程序在同一地址空间有多个不同的活动线程。

  3. 经济(Economy):进程创建所需要的内存和资源的分配比较昂贵。由于线程能共享它们所属进程的资源,所以创建和切换线程会更为经济。

  4. 可拓展性(Scalability):多线程的优点之一是能充分使用多处理器体系结构。以便每个进程能并行运行在不同的处理器上。不管有多少CPU,单线程进程只能运行在一个CPU上,在多CPU上使用多线程加强了并发功能。

二、多线程模型

     有两种不同的方法来提供线程支持:用户层的用户线程(User Threads)和内核层的内核线程。用户线程受内核支持,而无需内核管理;而内核线程由操作系统支持和管理。事实上所有当代操作系统都支持内核线程。

用户线程与内核线程具有的关系如下:

1.多对一模型:多对一模型将许多用户级线程映射到一个内核线程。线程管理由线程库在用户空间进行的,因而效率比较高。但是如果一个线程执行了阻塞系统调用,那么整个线程会阻塞。因为任意时刻只能有一个线程能够访问内核,多个线程不能并行运行在多处理器上。

2.一对一模型:一对一模型每个用户线程映射到一个内核线程。该模型在一个线程执行阻塞系统调用时,能允许另一个线程继续执行。它也允许多个线程能并行运行在多处理器系统上,这种模型的唯一缺点是每创建一个用户线程就会创建一个相应的内核线程。由于创建内核线程的开销会影响应用程序的性能,所以这种模型的绝大多数实现限制了操作系统所支持的线程数量。

3.多对多模型:多对多模型多路复用了许多用户线程到同样数量或更小数量的内核线程上。多对多模型没有这两者的缺点:开发人员可创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发执行。而且当一个线程执行阻塞系统调用时,内核能调度另一个线程来执行。

参考博客:http://blog.csdn.net/u013007900/article/details/49964515

2017-12-24 14:08:14 adminpd 阅读数 1150

一、实验目的

加深对进程概念的理解,明确进程与线程的区别。
掌握Linux进程创建和撤销的方法,进一步认识并发执行的实质。
了解多线程的程序设计方法。

二、实验项目内容

多进程和多线程
 1号进程创建2,3号两个进程
 2号进程创建两个线程Thread1,Thread2
 Thread1:求(1~n)之间的素数
 Thread2:生成Fibonacci序列
 3号进程创建4,5号两个进程
 4号进程执行系统命令,ls,ps,cp等
 5号进程执行一个用户编写的可执行文件
 每个进程输出自己的进程ID和父进程的进程ID,观察分析,并画出程序的进程树结构。

三、实验程序代码:

#include <stdio.h>
#include <math.h>
#include <sys/types.h>  
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

/**
* Attention to add -lpthread and -lm when compile
* eg : gcc process.c -lpthread -lm -o process
*/

//The thread to find the prime number between (1~n)
void *MyThread1(void* arg)
{
    int n;
    //Get the resolve range
    printf("input the value of n:\n");
    scanf("%d",&n);
    printf("the prime number between 1~%d is:\n",n);

    for(int m=2;m<=n;m++)
    {
        if (m<=3)
        {
            printf("%4d",m);
            continue;  
        }

       int k=sqrt(m);  
       for(int i=2; i<=k; i++)
       {
            // printf("m: %f\n",k);
            if(m%i==0) 
                break;  
            if(i>=k) 
                printf("%4d",m);  
       }
    }

    printf("\n thread1 exit!\n");
    pthread_exit(0);
}

//The thread to generate Fibonacci sequences
void *MyThread2(void* arg)
{
    int fib0=0,fib1=1,fib2,i,N;
    printf("input fib value N:\n");
    scanf("%d",&N);
    printf("the fib sequences as following:\n");
    for (int i = 0; i < N; i++)
    {
        if (i==0)
        {
            printf("0");
        }
        else if (i==1)
        {
            printf("1");
        }
        else
        {
            fib2=fib0+fib1;
            printf("%d",fib2 );
            fib0=fib1;
            fib1=fib2;
        }
    }
    printf("\n thread2 exit!\n");
    pthread_exit(0);
}

//No.2 process create two threads
int createThread()
{
    int ret1=0,ret2=0;
    //Define the id of thread
    pthread_t id1,id2;
    //Create thread1
    ret1=pthread_create(&id1,NULL,MyThread1,NULL);
    if (ret1)
    {
        printf("Create pthread error!\n");
        return 1;
    }
    //Create thread2
    ret2=pthread_create(&id2,NULL,MyThread2,NULL);
    if (ret2)
    {
        printf("Create pthread error\n");
        return 1;
    }

    //Mainthread wait for childthread
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);

    printf("main thread exit!\n");
    return 0;
}

//The Function of N0.4 and No.5
void childProcessfunc(int i)
{
    switch(i)
    {
        case 4:
            printf("\nThis is NO.4 process, ID is  %d, parent ID is %d, will create 2 thread\n",getpid(),getppid());
            printf("\nExcute system command ls:\n");
            system("ls");
            printf("\nExcute system command ps:\n");
            system("ps");
            printf("\nExcute system command cp:\n");
            system("cp sayhello.c sayhello.cpp");

            break;
        case 5:
            printf("\nThis is NO.5 process, ID is  %d, parent ID is %d, will create 2 thread\n",getpid(),getppid());
            printf("\nCarray out executable program:\n");
            system("./sayhello");
            break;
    }
    exit(0);
}

//No.3 process create two child process, N0.4 and No.5
int createProcess()
{
    int i;
    for (int i = 4; i <= 6; i++)
    {
        pid_t child=fork();
        //create process failed
        if (child==-1)    
        {
            printf("Error hanppened in fork function!\n");
            return 0;
        }
        //exert function when success
        else if(child==0)
        {
            childProcessfunc(i);
        }
    }

    for (int i = 0; i < 2; i++)
    {
        //Parent process waits child process
        pid_t tempPid=wait(NULL);
        printf("The process %d exit\n", tempPid);
    }

    return 0;
}

//The Function of N0.2 and No.3
void mainProcessfunc(int i)
{
    switch(i)
    {
        case 2:
            printf("\nThis is NO.2 process, ID is  %d, parent ID is %d, will create 2 thread\n",getpid(),getppid());
            createThread();
            break;
        case 3:
            printf("\nThis is NO.3 process, ID is  %d, parent ID is %d, will create 2 thread\n",getpid(),getppid());
            createProcess();
            break;
    }
    exit(0);
}

int main()
{
    int i;
    //Create two child process, No.2 and No.3
    for (int i = 2; i < 4; i++)
    {
        pid_t child=fork();
        if (child==-1)
        {
            printf("Error happened in fork function!\n");
            return 0;
        }
        else if(child==0)
        {
            // printf("process Id is %d:\n",getpid());
            mainProcessfunc(i);
        }
    }
    for (int i = 0; i < 2; i++)
    {
        //Parent process waits child process
        pid_t tempPid=wait(NULL);
        printf("\nThe process %d exit\n", tempPid);
    }
    //Root process exit
    printf("\nThe No.1 root process Id is %d exit\n",getpid());

    return 0;
}
注:在编译命令中添加-lpthread和 -lm ,-lpthread是表示要连接到pthread的库是这里省略的lib,因为在求素数算法中使用math库中sqrt函数,需要用到math.h,则加上 -lm。

四、实验结果

这里写图片描述

2019-04-14 20:32:05 z944733142 阅读数 32

人们引入进程解决了单处理机环境下多个程序并发执行的问题, 提高了资源利用率和系统吞吐量.
引入线程则是为了能够地提高程序并发执行程度,减少程序在并发时所付出的时空开销, 从而使OS具有更好的并发性

线程的引入

进程

进程是一个可拥有资源和可独立调度和分派的基本单位, 由于进程的这两个属性, 使进程成为一个能独立运行的基本单位, 也构成了进程并发执行的基础

进程并发所付出的开销

  1. 创建进程, 需为其分配资源, 如内存, I/O, 相应的PCB等
  2. 撤销进程, 又需将之前占有的资源进行回收, 将PCB撤销等
  3. 切换进程, 需要切换进程的上下文, 保留当前CPU的环境, 设置新的环境, 开销大.

进程的创建, 撤销, 切换开销都很大, 因此限制系统中进程数目, 所以限制了并发程度的进一步提高

线程 - 作为调度和分派的基本单位*

因此要设法将拥可有资源和可独立调度和分派这两个属性分开, 即拥有资源的单位不对其施以频繁的切换, 不把作为调度和分派的基本单位作为拥有资源的单位.
为了解决进程创建调度分派开销过大, 并发程度差的问题, 在OS中引入线程, 以线程作为调度和分派的基本代为, 可以有效改善系统的性能.

线程和进程的比较

线程又称为轻型进程或进程元

1. 调度的基本单位

在传统的OS中因为进程是调度的基本单位, 所以每次被调度时进行上下文切换, 开销大.
吧线程作为调度和分派的基本单位, 因而线程是能独立运行的基本单位, 当线程切换时仅需保存少量寄存器内容.
在同一进程中线程的切换不会引起进程的切换.

2. 并发性

引入线程后, 进程间不但可以并发执行, 一个进程中的多个线程之间也可以并发执行.同理多个进程中的多个线程也能并发执行.从而时OS具有良好的并发性, 更加有效地增加了系统的资源利用率和系统吞吐量.
如果一个进程要执行多个相似的任务, 如果传统的进程只能一次为一个客户服务, 如果设置多个线程则一次可以为多个客户服务.

3. 拥有资源

进程拥有资源, 并作为系统中拥有资源的一个基本单位
线程仅拥有一点必不可少的, 能保证其独立运行的资源, 例如 线程控制块TCB.程序计数器PC, 保留局部变量, 少数状态参数,返回地址等的一组寄存器和堆栈.
线程除了自己拥有少量的资源外, 还允许多个线程共享其进程的资源. 属于同一进程的所有线程具有相同的地址空间, 可以访问进程所有的资源, 如一打开的文件, 定时器, 信号量机制等内存空间和I/O设备等

4. 独立性

线程之间的独立性要比进程低得多, 因为进程拥有独立的地址空间和资源, 除了共享全局变量.
线程是为了提高并发性以及进行相互间的合作而创建的, 他们共享内存地址空间和资源, 如一个线程的堆栈可以被其他线程读写甚至清楚, 一个线程打开的文件可以供其他线程读写.

5. 系统开销

进程的创建和撤销需要系统为之分配回收PCB, 回收分配其他资源, 因此速度和开销明显大于线程, 而进程的切换涉及到上下文切换,.其效率也远低于线程.
因为同一个线程具有相同的地址空间, 所以线程之间的同步和通信也比进程简单

6. 支持多处理机

传统的进程无论有多少处理机, 只能在一个上运行. 对于多线程的进程, 可以将其中的多个线程分配到多个处理机上, 使他们并行执行.

线程的状态

1. 执行态

表示线程正在执行

2. 就绪态

线程已经就绪, 已经具备了执行条件, 一旦获取CPU即可执行

3. 阻塞态

线程因为某些事件而处于暂停的状态, 例如未获取到临界资源.

线程的状态和其切换与进程相似.

线程控制块TCB

同进程一样, 系统也为每个线程配置了一个线程控制块TCP, 用于管理和控制线程的信息记录在TCB中包括

  1. 线程标识符
    唯一表示线程的标识符
  2. 一组寄存器
    包括程序计数器PC, 状态寄存器和通用寄存器
  3. 线程运行状态
    描述线程正处于何种太子
  4. 优先级
    描述线程优先级程度
  5. 线程专有存储区
    用于线程切换时存放现场保护信息与线程相关的统计信息等
  6. 信号屏蔽
    对某些信号加以屏蔽
  7. 堆栈指针

多线程OS中的进程属性

多线程OS中的进程具有以下属性

  1. 进程仍是一个可拥有资源的基本单位.
  2. 多个线程并发执行, 通常一个进程都含有若干个相独立的线程, 且至少要有一个线程. 由进程为这些线程提供资源和运行环境
  3. 进程已经不是可执行的实体, 在多线程OS中, 把线程作为独立运行的基本单位. 但进程仍有相关的状态, 例如进程处于执行状态, 实际上是指进程中某线程正在执行, 而对进程所施加的对进程状态有关的操作也对线程起作用.
没有更多推荐了,返回首页