为什么引入线程呢?线程的特点是怎样的?

为什么要引入线程?

由于进程是资源的拥有者,在创建、撤销、切换等操作中需要较大的时空开销,限制了并发性的进一步提升。

为了减少进程切换的开销,将进程的资源分配单元和调度单元这两个属性分开处理,即进程仍然是资源分配的基本单位,

但不作为一个调度的基本单元(很少调度或切换),将调度执行和切换的责任分配给一个“线程”。

这样做的好处不仅可以提高系统的并发性,还可以适应新的对称多处理器(SMP)环境的运行线程标识符 有什么用,充分发挥其性能。

2.线程和进程的关系

例如,

线程是程序中的一个函数线程标识符 有什么用,进程是整个程序;

程序包含多个函数,已知一个进程包含多个线程;

进程中的线程同时运行,所以进程的总数是线程的时间是执行线程的最长时间,而不是所有线程时间的总和

1.linux中线程的概念

线程是OS调度和分派的基本单元,包含在进程中,线程是指进程内的一个执行单元。它的特点是什么?它比过程轻。进程的实体是程序,线程的实体是函数,共享进程的资源。进程中的多个线程可以并发执行,并且可以独立调度,只需要很少的资源。 .

Linux 早期不支持内核级线程,所以用户态 glibc 库以用户态线程的形式支持多线程。

每个线程都有一个id,我们称之为utid(或tid)。

后来的Linux内核支持多线程,一个进程中的所有内核线程都属于同一个组,并且每个内核线程都有一个唯一的编号,我们给它命名为ktid(用来区分用户态tid),所以一个线程有两个编号, utid 和 ktid。

这里要强调的是,用户级线程遵循 POSIX 标准并且是可移植的。

可以看到在Linux中,用户级线程和内核级线程是一对一的模型。

1.1 个线程标识符

与进程 ID 类似,线程也有一个线程 ID 来指示其身份。但有趣的是,这两者之间还是有一点区别的:

进程 ID 在整个系统中是唯一的,而线程 ID 仅在单个进程的上下文中是唯一的。线程ID仅在单个进程的上下文中是唯一的,也就是说,其他进程可能会生成与该进程相同的线程ID)

进程 ID 是整数值,但线程 ID 不一定是整数值。它很可能是一个结构。 (进程ID是整数值,线程ID不仅是整数值,还是结构体)

进程 ID 可以很容易地打印,而线程 ID 则不容易打印。 “的意思。因为线程在大多数情况下是一个结构,所以需要有一个函数可以比较两个线程。

#inlcude <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2); // 判断两线程是否是同一个

可以看到pthread_equal()函数接受两个线程参数,如果是同一个线程,返回非零值,否则返回0.

另一方面,我们有时需要知道这个线程的ID,在很多情况下,需要下面的函数

#include 
pthread_t pthread_self(void); // 返回自己的线程ID

2.linux中的线程创建2.1线程创建函数

Linux实现线程由两部分组成:

内核线程支持 + 用户模式库支持 (glibc)。

Linux下调用glibc库中的pthread_create()函数创建线程。可以通过 man pthread_create 查看它的使用情况:

NAME
       pthread_create - create a new thread
SYNOPSIS
       #include 
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
     void *(*start_routine) (void *), void *arg);
       Compile and link with -pthread.

这个函数有四个参数,

第一个参数pthread_t *thread是线程id的地址,

第二个参数const pthread_attr_t *attr是线程的属性,这里传了一个NULL指针,

第三个参数void *(*start_routine)(void *)很重要,它是线程实体函数的名字,

第四个参数 void *arg) 是传递给这个函数的参数。

通常当一个程序被创建并成为一个进程时,它会从默认线程开始。因此,我们可以说每个进程至少有一个控制线程,并且一个进程可以使用以下函数 Thread 创建额外的线程:

#include 
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);

参数说明:

2.2线程函数示例

#include 
#include 
#include  // for thread
#include  
#include 
pthread_t tid[2];  // 创建两个线程
void *doSomeThing(void *arg)
{
    unsigned long i = 0;
    pthread_t id = pthread_self();
    if(pthread_equal(id, tid[0]))
    {
        printf("\n First thread processing\n"); // 第一个线程正在执行
    }
    else
    {
        printf("\n Second thread processing\n");
    }
    
    for(i=0; i<(0xFFFFFFFF); i++);
    return NULL;
}
int main(void)
{
    int i = 0;
    int err;
    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if(err != 0) printf("\n can't create thread:[%s]", strerror(err));
        else printf("\n Thread created successfully\n");
  
        i++;
    }
    sleep(5);
    return 0;
}

有两点不太合理:

doSomeThing() 函数末尾的“return NULL”;

主函数“sleep()”在.

上面代码的解释:

pthread_create()函数用于创建两个线程;

两个线程的启动函数都是doSomeThing();

在doSomeThing()函数中,线程使用pthread_self()和pthread_equal()来确认是在执行线程1还是线程2;

doSomeThing() 函数中的 for 循环只是浪费一点时间。

2.3线程函数编译

需要在编译时添加线程相关代码——lpthread选项声明需要连接线程库,以便调用头文件pthread.h中声明的函数。

编译后应该如下图所示。

gcc threadExample.c -o threadExample1 -lpthread

这是因为pthread库不是Linux系统的默认库,所以在使用pthread_create()函数创建线程时要链接静态库libpthread.a。

:~/Documents/course_2610/lab11_thread$ ./threadExample1 
 Thread created successfully 
 The first thread processing 
 The  second thread processing 
 Thread created successfully 

3. 总结

1)什么是用户模式线程,由谁创建,谁管理这些线程?为什么说用户模式线程是可移植的?

2)谁管理内核级线程?

3)进程和线程共享哪些资源,哪些不能共享?

4)运行第6步的代码,分析程序的结果。你有什么灵感?

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论