线程控制(一):线程创建与终止

26 篇文章 0 订阅
19 篇文章 0 订阅

说在前面:与线程相关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的,要想使用这些函数,要通过引入头文,链接这些线程函数库时要使用编译器命令的“-lpthread”选项

创建线程

//创建一个新的线程
int  pthread_create(pthread_t  *threade,const pthreade_attr_t  *attr,void *(*start_routine)(void*),void  *arg);
//参数:thread:返回线程ID
//attr:设置线程的属性,attr为NULL时表示使用默认属性
//start_routine:是个函数地址,线程启动后要执行的函数
//arg:传给线程启动函数的参数
//返回值:成功返回0,失败返回错误码

错误检查:我们常见的一些函数都是成功返回0,失败返回-1。并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做),而是将错误码以返回值的形式返回。pthreads同样也提供了线程内的errno变量,以支持其他使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小。
现在我们可以用这个函数试试创建一个线程:

//Makefile文件内容:
thread:thread.c
    gcc -g $^ -o $@ -lpthread

.PHONY:clean

clean:
    rm -rf thread

//thread.c文件内容:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
//创建出来一个线程时,该线程做的工作有该函数定义
void *rout(void *arg)
{
    (void)arg;
    for(;;)
    {
        printf("I am thread 1\n");
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    //创建成功返回0,失败返回错误码
    int ret = pthread_create(&tid,NULL,rout,NULL);
    if(ret != 0)
    {
        //创建失败
        fprintf(stderr,"pthread_create:%s\n",strerror(ret));
        exit(EXIT_FAILURE);
    }
    //创建成功
    for(;;)
    {
        printf("I am main thread\n");
        sleep(1);
    }
    return 0;
}

这里写图片描述
由图可以看出,成功创建了一个线程。

进程ID和线程ID:在Linux中,目前的线程实现是Native POISX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程,每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID,但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在求进程内的所有线程调用getpid函数是返回相同的进程ID,为了解决这个问题,由此引入了线程组的概念。
所谓的线程组就是多线程的进程,线程组内的每一个线程在内核之中都存在的一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看是对应的进程ID,其实他对应的是线程ID;进程描述符中的tgid(Thread Group ID),该值对应的是用户层面的进程ID。
这里写图片描述
现在说的 线程ID不同于pthread_t类型的线程ID,和进程ID一样该线程ID是pid_t类型的变量,而且是用来唯一标识线程的整型变量。
命令查看线程ID:(以上文提到的创建线程的代码为例)
这里写图片描述
ps命令中的-L选项会显示:LWP(线程ID),即gettid()系统调用的返回值。
NLWP,线程组内线程的个数。
从图中我们可以观察到,thread进程是多线程的,进程的ID是73917。
进程内有两个线程(NLWP=2),线程ID分别为73917和73918(LWP所对应的值)。
当然我们也发现,进程ID与其中一个线程ID相等了,事实上,线程组内的第一个线程,在用户态被称为主线程,在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的ID,group_leader的指针则指向自身,即主线程的进程描述符,所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。对于线程组内的其他线程的ID就有内核具体分配。其中只需要明确一点的就是,主线程的ID总是与线程组的ID保持一致,无论是主线程直接创建线程,还是创建出来的线程再次创建的线程。

线程ID及进程地址空间布局
回顾一下pthread_creat函数,该函数调用成功以后会产生一个线程ID,并且将其存放在第一个参数(pthread_t thread)指向的地址中。然而该线程ID与刚刚前面讲过的线程ID却不是一回事儿。前面讲的线程ID属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程。而我们的pthread_creat函数产生并且标记在第一个参数指向的地址中的线程ID,属于NPTL线程库的范畴。线程库的后续操作就是根据该线程ID来操作线程的。对于目前Linux实现的NPTL,pthread_t类型的线程ID本质就是一个进程地址空间上的一个地址。

线程终止
终止线程而不终止进程的方法

1、从线程函数return,这种方法对主线程不适用,从main函数return相当于调用exit
2、线程可以调用pthread_exit终止自己
3、一个线程可以调用pthread_cancel终止同一进程中的另一个线程

pthread_exit函数:

//终止线程
void pthread_exit(void *value_ptr);
//参数value_ptr:不要将其指向一个局部变量
//无返回值,和进程一样线程结束的时候无法返回到他的调用者(自身)

注意:该函数或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针的时候该线程已经退出了。

pthread_cancel函数:

//取消一个执行中的线程
int pthread_cancel(pthread_t thread);
//参数:thread为线程ID
//pthread_self()函数可以获取自身的线程ID
//返回值:成功返回0,失败返回错误码
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值