操作系统~Linux~线程控制,POSIX线程库的使用

28 篇文章 0 订阅
17 篇文章 0 订阅

1.POSIX线程库

  1. 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”开头的
  2. 要使用线程库中的函数,要通过引入头文件<pthread.h>
  3. 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

2.创建线程-pthread_create()

功能:创建一个新的线程

原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *

(*start_routine)(void*), void *arg);

参数

  1. thread:是一个输出型参数,返回线程ID,常用来保存线程id(无符号长整形)
  2. attr:设置线程的属性,attr为NULL表示使用默认属性,一般都是NULL
  3. start_routine:是个函数地址,线程启动后要执行的函数
  4. arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码

pthread_self函数

pthread_self()函数可以返回当前线程的id,这是一个地址,所以我们用十六进制打印这个地址

代码举例

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdlib>
#include <cstring>
using namespace std;

void *thread_run(void* args)
{
    char *s = (char*)args; //首先要将void*的参数转成对应的类型
    while(1)
    {
        cout << "Get the param :" << s << endl;
        printf("new thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

int main()
{
    char *s = new char[6];
    strcpy(s,"zebra");
    pthread_t tid;
    pthread_create(&tid, NULL, thread_run, s);


    while(1){
        printf("main thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

结果分析

        可以看到,主线程的线程id和新线程的线程id是不同的,而且是地址,同时我们可以发现参数zebra成功传递到了thread_run函数中。


3.线程等待-pthread_join(主线程等待新线程)

        一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于"僵尸进程"的问题

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

功能:等待线程结束,调用该函数的线程将挂起等待,直到id为thread的线程终止(也就是主线程阻塞式等待id为thread的进程终止)

原型

int pthread_join(pthread_t thread, void **value_ptr);

参数

  1. thread:线程ID
  2. value_ptr:是一个输出型参数,返回线程执行完函数后的返回值(这个函数的返回值也就是这个线程的返回值),我们创建函数的时候,传入的线程执行函数返回值是void*的,所以这里定义一个指向void*类型变量的指针。

返回值:成功返回0;失败返回错误码

e.g.:主线程等待其他线程执行完毕后才能继续执行(只能用这种for循环的方式,一个个等):

注:如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许。

所以如果在c++中,如果我们要使用void*类型的8个字节存储实际内容(void*类型本身就可以充当容器),我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节

代码举例(使用void*存储参数的值)

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
const int NUM = 10;
void *run(void *param)
{
    // int data = *(int*)param;  //如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许
    // 所以如果在c++中,我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节
    long data = (long)param;
    printf("current thread is %d\n",data);
    return (void*)data;
}

int main()
{
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
        pthread_create(&tid[i],NULL,run,(void*)i);
        usleep(100000);
    }
    void *status = NULL;
    int ret = 0;
    for(int i = 0; i < NUM; i++)
    {
        ret = pthread_join(tid[i],&status);
        printf("ret: %d, status: %d\n", ret, (long)status);
    }

    return 0;
}

结果分析

从结果中可以看出

        1.我们将参数i转换成void*类型,传到run方法内部,然后强转成long后,可以成功获取到参数i的值。

        2.pthread_join函数可以等待线程,并通过一个输出型参数获取函数的返回值,也就是线程的返回值。

注意:我们创建线程执行完对应的函数后,有三种情况:

1.代码跑完结果对

2.代码跑完结果不对

3.代码异常了

前两种pthread_join可以通过输出型参数status来判断代码结果是否正确,并作出处理

第三种代码异常,pthread_join不需要处理,处理异常是进程来做的。


 4.线程终止的方案

1.函数中return

        a. main函数退出return的时候代表主线程and进程退出

        b.其他线程函数return,只代表当前线程退出

2.新线程通过pthread_exit终止自己( vs exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! !)

3.一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

(1).pthread_exit函数

功能:终止调用该函数的线程

原型

void pthread_exit(void *value_ptr);

参数

value_ptr:value_ptr不要指向一个局部变量,一个输入型参数。这个参数会作为线程退出后的返回值

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

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


(2).pthread_cancel函数

功能:取消一个执行中的线程,取消线程ID为传入参数的线程

原型

int pthread_cancel(pthread_t thread);

参数

thread:线程ID(可以在创建的时候用变量把线程id保存下来,在这里就可以拿来用

返回值:成功返回0;失败返回错误码

注意事项:

取消线程以后,线程退出的返回值是-1(对应的是PTHREAD_CANCELED宏)


5.线程分离-pthread_detach函数

  1. 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  2. 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

线程分离,分离之后的线程不需要被join(不能被join)运行完毕之后,会自动释放Z状态的pcb

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());


6.pthread中的线程id与Linux内核中的轻量级线程id的区别

        我们看到的库里面的线程id是pthread库的线程id,不是Linux内核中的LWP(light weight process),pthread库的线程id是一个内存地址,而Linux内核中的LWP是一个数值。

1.我们使用多线程需要用到pthread库,这是一个采用动态链接的共享库,加载到内存最后都放在栈和堆之间的共享区(共享内存,动态库都是放在这里的),内存中只有一份。(通过线程自己的页表映射到同一个物理空间)

        每个线程都要有运行时的临时数据,每个线程都要有自己的私有栈结构,描述线程的用户级控制块。

2.每一个新线程创建的时候,都会在pthread库里创建一个pthread结构体(每个线程都使用虚拟地址空间-mm_struct,注意task_struct包括mm_struct,每个线程有一个task_struct也就是PCB,其内部的mm_struct里面的共享区里面存放有所有线程的pthread结构体),用来保存线程的相关信息(线程的用户级数据,线程的私有栈),有100个线程,那在共享区内部的pthread库里面就会有100个用来保存pthread信息的结构体。

        在这个pthread库中,如何快速找到对应的pthread结构体呢,只要拿到线程id就行,线程id就是pthread结构体的地址,只要拿到这个地址,我们就可以很轻松地找到pthread,获取线程运行时的用户级数据。

3.用户层调用pthread_create创建一个线程的时候,会在共享区内部创建一个pthread结构体,保存线程的栈,用户级数据(临时数据)等信息;返回给用户的线程id就是这个pthread结构体的地址。同时,在Linux内核中,也要为线程创建对应的pcb(CPU调度由pcb说了算)。其中,也要创建一个与当前线程id对应的lwp(在pthread结构体里面保存lwp,内核的pcb里面保存线程id,也就是pthread结构体的地址)。

用户级线程1:1式的和Linux内核中的轻量级线程对应,这就是Linux实现线程的方式。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值