Linux并发程序设计(5):线程基础

目录

一、介绍

1.1 线程与进程的区别

1.2 线程特点

1.3 线程共享资源

1.4 线程私有资源

1.5 Linux线程库

二、相关函数

2.1 线程创建 – pthread_create

2.2 线程结束 – pthread_exit

2.3 线程查看tid函数

2.4 线程回收

2.4.1 使用pyhread_join进行线程回收

2.4.2 使用线程分离进行线程回收

2.5 取消一个线程

2.5.1 取消一个线程

2.5.2 手动设置取消点

2.5.3 取消状态以及类型

2.6 线程的清理 

三、代码测试

3.1 线程的创建与参数传递 

3.2 线程回收

3.2.1 使用pthread_join进行线程回收

3.2.2 通过pthread_detach进行线程回收

3.2.3 通过线程属性进行线程回收


一、介绍

1.1 线程与进程的区别

进程

进程有独立的地址空间;

Linux为每个进程创建task_struct;

每个进程都参与内核调度,互不影响;

进程在切换时系统开销大。

线程

很多操作系统引入了轻量级进程(LWP);

同一进程中的线程共享相同地址空间。

  • 1.2 线程特点

通常线程指的是共享相同地址空间的多个任务;

使用多线程的好处:

        大大提高了任务切换的效率;

        避免了额外的TLB & cache的刷新。

1.3 线程共享资源

一个进程中的多个线程共享以下资源:

  • 可执行的指令
  • 静态数据
  • 进程中打开的文件描述符
  • 当前工作目录
  • 用户ID
  • 用户组ID
  • 1.4 线程私有资源

每个线程私有的资源包括:

  • 线程ID (TID)
  • PC(程序计数器)和相关寄存器
  • 堆栈
  • 错误号 (errno)
  • 优先级
  • 执行状态和属性 

1.5 Linux线程库

pthread线程库中提供了如下基本操作

  • 创建线程
  • 回收线程
  • 结束线程

同步和互斥机制

  • 信号量
  • 互斥锁

二、相关函数

2.1 线程创建 – pthread_create

 #include  <pthread.h>

 int  pthread_create(pthread_t *thread, const
       pthread_attr_t *attr, void *(*routine)(void *), void *arg);
  • 成功返回0,失败时返回错误码
  • thread 线程对象
  • attr 线程属性,NULL代表默认属性
  • routine 线程执行的函数
  • arg 传递给routine的参数 ,参数是void * ,注意传递参数格式。

2.2 线程结束 – pthread_exit

 #include  <pthread.h>
 void  pthread_exit(void *retval);
  • 结束当前线程
  • retval可被其他线程通过pthread_join获取
  • 线程私有资源被释放

2.3 线程查看tid函数

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

2.4 线程回收

使用pthread_create创建的线程有两种状态:joinable和unjoinable。默认是joinable 状态。 对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放,故需要针对这个问题进行处理。pthread_detach()和pthread_join()就是控制子线程回收资源的两种不同的方式。

2.4.1 使用pyhread_join进行线程回收

如果是joinable状态,则该线程结束后(通过pthread_exit结束或者线程执行体任务执行完毕)不会释放线程所占用堆栈和线程描述符等资源,需要在主线程调用了pthread_join函数之后才会释放。pthread_join函数一般应用在主线程需要等待子线程结束后才继续执行的场景(pthread_join是一个阻塞函数,调用方会阻塞到pthread_join所指定的tid的线程结束后才被回收)。

 #include  <pthread.h>
 int  pthread_join(pthread_t thread, void **retval);
  • 成功返回0,失败时返回错误码
  • thread 要回收的线程对象
  • 调用线程阻塞直到thread结束
  • *retval 接收线程thread的返回值

2.4.2 使用线程分离进行线程回收

如果是unjoinable状态,则该线程结束后会自动释放占用资源。实现方式是在创建时指定属性,或者在线程执行体的最开始处添加一行:pthread_detach(pthread_self());这种方式不会阻塞,调用它后,线程运行结束后会自动释放资源,。

方式一: 

int pthread_detach(pthread_t thread);    
  • 成功:0;失败:错误号
  • 指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程)

方式二: 

pthread_attr_t attr;            /*通过线程属性来设置游离态(分离态)*/
//设置线程属性为分离
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

2.5 取消一个线程

2.5.1 取消一个线程

在程序运行过程中,存在向一个线程发送请求它立即退出的操作,这种操作被成为取消线程。

int pthread_cancel(pthread_t thread);     杀死一个线程

注:线程的取消要有取消点才可以,线程的取消点主要是阻塞的系统调用 

2.5.2 手动设置取消点

如果没有取消点,手动设置一个

void pthread_testcancel(void);

2.5.3 取消状态以及类型

默认情况下,线程会响应其它线程发送的取消请求的,响应请求然后退出线程。当然,线程可以选择不被取消或者设置取消方式,通过 pthread_setcancelstate()和 pthread_setcanceltype()来设置线程的取消性状态和类型。

int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
  • PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
  • PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED                
PTHREAD_CANCEL_ASYNCHRONOUS     
  • PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点为止,这是所有新建线程包括主线程默认的取消性类型(所谓取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点);
  • PTHREAD_CANCEL_ASYNCHRONOUS:在其他时间点取消。

2.6 线程的清理 

必要性: 当线程非正常终止,需要清理一些资源。 

void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)

routine 函数被执行的条件:

  • 被pthread_cancel取消掉。
  • 执行pthread_exit
  • 非0参数执行pthread_cleanup_pop()

注意: 

  • 必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。
  • pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行. 
  • pthread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反
  • 线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。 

三、代码测试

3.1 线程的创建与参数传递 

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

void *testThread(void *arg)
{
    printf("This is a %s,pid=%d,tid=%lu\n",(const char*)arg,getpid(),pthread_self());
    pthread_exit(NULL);
    printf("after pthread exit\n");
}
int main()
{
    pthread_t tid;
    int ret;
    ret = pthread_create(&tid,NULL,testThread,"new Thread");

    printf("This is main thread,pid=%d,tid=%lu\n",getpid(),pthread_self());
    sleep(1);
    return 0;
} 

实验截图 

从实验截图可以看出,线程已经成功创建,并且可以看到,主线程和子线程都为同一个pid进程下,但是有各自的进程tid 。

3.2 线程回收

3.2.1 使用pthread_join进行线程回收

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg)
{
    printf("This is child thread\n");
    sleep(25);
    pthread_exit("thread return");
}

int main()
{
    pthread_t tid[100];
    void *retv;
    int i;
    for(i=0;i<5;i++)
    {
        pthread_create(&tid[i],NULL,func,NULL);
    }
    for(i=0;i<5;i++)
    {
        pthread_join(tid[i],&retv);
        printf("thread ret=%s\n",(char*)retv);
    }
    while(1)
    {
        sleep(1);
    }
    return 0;
}
              

实验截图 

从实验截图可以看出,子线程在结束后,成功被pthread_join回收。

3.2.2 通过pthread_detach进行线程回收

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

void *func(void *arg)
{
    printf("This is child thread\n");
    sleep(25);
    pthread_exit("thread return");
}

int main()
{
    pthread_t tid[100];
    void *retv;
    int i;
    for(i=0;i<5;i++)
    {
        pthread_create(&tid[i],NULL,func,NULL);
        pthread_detach(tid[i]);
    }

    while(1)
    {
        sleep(1);
    }
    return 0;
}

3.2.3 通过线程属性进行线程回收

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

void *func(void *arg)
{
    printf("This is child thread\n");
    sleep(25);
    pthread_exit("thread return");
}

int main()
{
    pthread_t tid[100];
    void *retv;
    int i;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

    for(i=0;i<5;i++)
    {
        pthread_create(&tid[i],NULL,func,NULL);
    }

    while(1)
    {
        sleep(1);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值