Linux环境下编程(二)——线程基础概念

上一篇讲了进程的基本概念,这一篇讲线程的。

基础概念

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

经常被问到的一个问题是:线程和进程有什么区别?为什么要有线程这样一个东西?

线程是运行在进程上下文中的逻辑流,最开始的时候一个进程对应一个主线程。后来觉得既然好多资源是可以共享的,为什么每次都要创建一个新的进程?不断地去切换进程的上下文造成了很多不必要的开销。既然有的资源可以共享,那么我们干脆弄一个轻量级的进程,这样切换起来岂不是快得多?而且现在操作系统都是多核的,既然它有几个脑子,那么每个脑子绑定一个线程,然后共同支配一个身体,岂不是更快?(当然了这段话说的并不是太严谨,仅供理解)。


同一进程里的线程,哪些资源是独占的,哪些资源是共享的?

独有的:线程ID、栈、栈指针、程序计数器、条件码、通用目的寄存器。

共享的:除了上述外,进程虚拟地址里的其它资源。


下面讲讲具体的应用

Posix线程是在C语言线程的标准接口。

一个线程对应一个线程id,它的类型是pthread_t,是一个自然数,但是这个并不是int型,而是无符号长整型,打印的时候要用%lu。

线程获取自身id的函数为:

#include <pthread.h>
pthread_t pthread_self(void);


线程的创建接口为:

#include<pthread.h>
typedef void* (func)(void *);
int pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg)

pthread_create和进程的创建函数有些区别,它的返回值不是线程的id,而是返回0或者非0,0代表创建成功,非0代表创建失败。

我们来看它的参数列表,第一个参数tid,就是我们传入的tid的地址,然后函数结束后,tid的值将会变成子进程的id。

第二个参数为线程创建的一些选项,默认是NULL。

第三个参数为一个函数地址,func*为函数指针,线程就从这个地址开始执行。

第四个参数为传递给线程函数的参数,如果线程函数需要多个参数的话,那么就把它作为结构体的形式传入。


下面来看一个具体实例:


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

typedef void* (func)(void *);

void error_msg(int ret, char *msg){//错误处理
    printf("%s: %d", msg, strerror(ret));
}

void Pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg){//包裹函数
    int ret;
    if ( (ret = pthread_create(tid, attr,f , NULL )) != 0){
        error_msg(ret, "create pthread faild");
        exit(0);
    }
}

void* pthread_instance(void* args){//线程实例
    printf("Hello, I am a pthread\n, my tid is %lu\n", pthread_self());
    return NULL;
}

int main(void){
    pthread_t tid;
    Pthread_create(&tid, NULL, pthread_instance, NULL);//创建线程
    printf("my child tid is %lu, my tid is %lu\n", tid, pthread_self() );

    sleep(1);//让主线程慢点执行
    exit(0);

    return 0;
}

运行结果:

结果分析:

1、在用gcc编译的时候,需要在后面加上动态链接库

2、为什么程序的倒数第三行需要加上sleep(1)?假如我把sleep(1)给注释掉,会出现什么情况?

注释掉sleep(1)

运行结果:

我们发现,子线程(也可以叫对等线程)的printf语句并没有被打印出来,这个到底是为什么?

其实,在主线程中的exit语句不仅将主线程退出了,更重要的是它终止了进程,当进程没有了,依赖它的子线程自然就不能够正常运行,所以那句话没有打出来。

如果我们加上了sleep(1)主线程休眠了1下,这个时候子线程先正常执行完毕,然后接着主线程也执行完毕,所以会打印两个printf出来。

对等线程(就是主线程创建的线程)的回收

很显然,上面的sleep(1)造成了一个问题,就是很多时候我们根本不知道主线程和对等线程的运行时间,只是通过sleep人为地去判断时间,是不是有点不妥当?

这个时候我们有两个方法来处理:

1、采用pthread_join函数,

#include<pthread.h>
int pthread_join(pthread_t tid, void **thread_return);
成功则返回0,不成功则返回非0.

线程通过调用phread_join 来等待其它线程终止,pthread_join函数会阻塞,直到tid终止,将线程调用返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源。

具体实验如下:

et, char *msg){
    printf("%s: %d", msg, strerror(ret));
}

void Pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg){
    int ret;
    if ( (ret = pthread_create(tid, attr,f , NULL )) != 0){
        error_msg(ret, "create pthread faild");
        exit(0);
    }
}

void Pthread_join(pthread_t tid, void **thread_return){
    int ret;
    if ( (ret = pthread_join(tid, thread_return)) != 0){
        error_msg(ret, "join faild");
        exit(0);
    }
}


void* pthread_instance(void* args){
    printf("Hello, I am a pthread, my tid is %lu\n", pthread_self());
    return NULL;
}

int main(void){
    pthread_t tid;
    Pthread_create(&tid, NULL, pthread_instance, NULL);
    printf("my child tid is %lu, my tid is %lu\n", tid, pthread_self() );
    Pthread_join(tid, NULL);
    exit(0);
    //pthread_detach(tid);
    //pthread_exit(NULL);
    return 0;
}

运行结果:

结果分析:

1、是不是和上面结果一样?主线程回收了pthread_join等待的对等线程资源后才退出。

2、大家注意到了我下面注释掉的两行代码没有?

线程的状态分为可结合的(joinable)和可分离的(detached)。一个可结合的线程可以被其它的线程终止和杀死,在被其它线程回收之前,它的资源不会被释放;一个可分离的线程,它不能够被其它的线程终止或释放,它的资源会在线程结束后由系统自动释放。

分离一个进程的函数为下,成功返回0,出错返回非0。

#include<pthread.h>
int pthread_detach(pthread_t tid);

那么,有什么办法能够让主线程在终止的时候先等待对等线程终止呢?有的,这个就是pthread_exit干的事情了。

pthread_exit函数,若成功则返回0,出错返回非0。

#include<pthread.h>
phread_exit(void *thread_return);

大家可以试试,是不是把Pthread_join注释掉,然后采用pthread_detach和pthread_exit之后结果一样。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值