<3>. Linux 线程控制

  1. POSIX线程库

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

  1. 创建线程

  1. pthread_create

pthread_create    创建一个新线程
#include<pthread.h>    引入头文件
int pthread_create(pthread_t * thread, const pthread _attr_t *attr, void*(*start_routine)(void*),void *arg)
返回值 0/error number
第一个参数:线程id
第二个参数:线程属性,默认为nullptr
第三个参数:返回值void* 参数为void* 的函数指针,一个线程要执行线程代码的一部分,对应的入口函数
第四个参数:传给函数指针的参数

创建线程成功后,会回调执行回调方法,*arg参数会位给回调

主线程向下执行,create函数内部会创建新线程成功后,执行回调方法,线程把函数执行完,线程退出

  1. 错误检查:

传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。 pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回 pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误, 建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

  1. pthread库

  1. 执行下面的代码,认识进程

#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int x=100;
void show(const string &name)
{
    cout << name << ", pid: " << getpid() << " " << x << "\n"
         <<  endl;
}
void *threadRun(void *args)
{
    const string name = (char *)args;
    while (true)
    {
        show(name);
        sleep(1);
    }
}
int main()
{
    pthread_t tid[5]; // 创建5个线程
    char name[64];  //为snprintf提供的缓冲区
    for (int i = 0; i < 5; i++) 
    {
        snprintf(name, sizeof name, "%s-%d", "thread", i); //将thread和i格式化到缓冲区作为每一个线程的线程名
        pthread_create(tid + i, nullptr, threadRun, (void *)name); //线程id,输出型参数
        sleep(1); // 缓解传参的bug
    }
    while (true)
    {
        cout << "main thread, pid: " << getpid() << endl;
        sleep(3);
    }
    return 0;
}

执行这条监视命令

while : ;do  ps axj|head -1&&ps axj|grep mythread |grep -v grep;sleep 1;echo "**********************";done

无论是主线程还是新线程 pid都是一样的,同时终止pid就终止了全部,当然,我们还发现了我们的代码打印的时候还是会出现时序问题

ps -aL用这条命令查看线程

while : ;do  ps -aL|head -1&&ps -aL|grep mythread |grep -v grep;sleep 1;echo "**********************";done

由次可以得出:在Linux下CPU是根据LWP去调度线程的

  1. 线程之间的关系

执行如下错误代码

#include <iostream>
#include <thread>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>

using namespace std;
__thread int g_val = 0;//修饰全局变量,带来的结果就是让每一个线程各自拥有一个全局的变量 -- 线程的局部存储
// 1. 线程谁先运行与调度器相关
// 2. 线程一旦异常,都可能导致整个进程整体退出
// 3. 线程的输入和返回值问题
// 4. 线程异常退出的理解
void *threadRoutine(void *args)
{
    pthread_detach(pthread_self());
    while(true)
    {
        cout << (char*)args << " : " << g_val << " &: " << &g_val << endl;
        g_val++;
        sleep(1);
        int a = 10;
        a /= 0;
        break;
    }
    pthread_exit((void*)11);//线程分离后不再关心
}

int main()
{
    // 因为我们目前用的不是Linux自带的创建线程的接口,我们用的是pthread库中的接口!
    pthread_t tid; // 本质是一个地址!
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
        cout << "main thread" << " : " << g_val << " &: " << &g_val << endl;
        sleep(100);
        break;
    }
    int n = pthread_join(tid, nullptr);
    cout << "n :" << n << "errstring: " << strerror(n) << endl;
 return 0;
}

分析:

如果需要只终止某个线程而不终止整个进程,可以有三种方法: 1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。 2. 线程可以调用pthread_ exit终止自己。 3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

线程回调函数 void * threadRoutine(void *args)

线程执行完返回一个特定值给主线程,主线程调用pthread_join可以得到

参数由主线程调用create函数给新线程,主线程调用join获得新线程的执行结果

如果新线程在堆中申请一段空间想要数据私有化,用完就delete,在线程内部new空间,把自己的计算任务完成可以通过这段空间把数据返回给主线程

pthread_exit函数

注:exit(退出码),这个函数调用会让进程退出

这个函数的返回值是void*,回调函数本身就应该返回void*,就相当于return啦

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
功能:线程终止 原型 void pthread_exit(void *value_ptr); 参数 value_ptr: value_ptr不要指向一个局部变量。 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

pthread_cancel函数

主线程取消新线程,主线程如果调用join获得的退出码为-1

功能:取消一个执行中的线程 原型 int pthread_cancel(pthread_t thread); 参数 thread:线程ID (tid) 返回值:成功返回0;失败返回错误码

线程被取消,join的时候,退出码是-1 #define PTHREAD_CANCELED ((void *) -1)

join提取到PTHREAD_CANCELED 就是-1

pthread_cancel如果一个线程没有起来,那他还是未定义的,取消的行为也是未定义的,所以不要刚创建立刻cancel,所以这个函数用于创建一个线程,线程跑了一段时间,你再取消他

线程等待 pthread_join函数 ——为什么需要线程等待?

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间

必须线程等待,不等会有内存泄漏问题,如果不关心新线程,则可以线程分离,如果采用了join线程分离,便不可以join否则出错

线程等待不需要关系新线程是否异常,因为一崩全崩,信号,是以进程为载体发送的

功能:等待线程结束 原型 int pthread_join(pthread_t thread, void **value_ptr); //采用阻塞等待回收资源 参数 thread:线程ID ,你要等的线程 value_ptr:它指向一个指针,后者指向线程的返回值 ,保存线程的退出结果 返回值:成功返回0;失败返回错误码
void* ret=nullptr;
pthread_join(tid,&ret);//linux下为8byte

这个代码线程内部的回调函数发生了除0错误,执行结果:

首先:验证了__thread修饰的全局变量每个线程独享,打印出来的地址不一样

然后线程内部发生了除0错误,整个程序就终止了

这里除0错误本质我们知道产生硬件异常,状态寄存器中溢出标志位被设置,所以之前说的线程专有的数据指的是运行过程中产生的临时数据,一些状态寄存器是大家共享的

  1. 线程ID及进程地址空间布局

线程id,很大的整数,本质是一个地址,地址的本质是线性连续的数字,具有唯一性

线程在库内部对应的相应属性集合的起始地址tid,也就是一个结构体对象的地址

tid:每个线程都有基本的描述线程的子集字段,库被映射到地址空间中,地址是线性的,为了让每一个线程竟快找到自己的属性字段,用线程的起始地址充当线程的tid

线程的局部存储:__thread 修饰全局变量,让每一个线程各自拥有这个全局变量

__thread是pthread为gcc和g++提供的编译选项,在编译时你创建几个线程就把全局变量为你拷贝几份

线程的局部存储,你若创建一个线程,包括新线程:

  1. 在线程的空间中开一块空间

  1. 把数据拷过来,下次你就访问你拷过来的数据

线程栈:在库中共享区提供的,为你维护的栈结构

内核只有轻量级进程,用户需要的线程是线程库对底层轻量级进程的封装
id,自身属性,线程都要被管理
1.由操作系统完成对轻量级线程的调度和内核数据结构的管理/内核级线程/
2.由线程库给用户提供相关线程的属性字段,包括线程id,线程栈的大小/用户层在库中对线程的管理/
因为采用用轻量级进程模拟线程的方式不能完全表征线程,所以由第三方线程库加以补充

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID 不是一回事。 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要 一个数值来唯一表示该线程。 pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

pthread_t pthread_self(void);

代码区是所有线程共享的,一个线程一个入口函数,跳转到共享区,因为新线程自己的独立栈结构在线程库中维护,而动态库被加载到共享区,所以进程执行过程中创建,等待,终止的代码的实现细节,线程id,退出结果等属于线程的局部变量都存在于共享区中线程的独立栈结构中!

新线程用共享区提供的私有栈结构,如果进程是但执行流就用内核为进程提供的栈区

pthread库从磁盘加载到内存,在创建PCB时调用clone,在自己的库中申请线程相关的属性字段,把线程栈的地址传给新线程,新线程使用共享区的栈区

  1. 线程程序替换

如果在线程这里调用execl,会导致当前程序整体被替换

除了主线程,其他终止,主线程去执行被替换后的代码,之前执行的代码如同要用exit

  1. 线程分离pthread_detach

如果我们不关心子进程的执行结果可以采用sigchild信号忽略,那线程的处理方法呢?

int pthread_detach(pthread_t thread)

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

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

pthread_detach(pthread_self())

线程启动后,立刻执行线程分离,让线程处于分离状态,线程退出后,由库自动回收线程曾经申请的资源,此时主线程不能join,会报错

在进行编程时,采用让主/父最后退出,分离场景:父/主线程不退出,服务器处理用户请求,派线程执行,采用线程分离的策略,新线程将自己分离,再pthread_exit()退出,主线程不用join,如果join返回值为非法参数

若主线程先退出,则进程退出

3. 用C++提供的接口创建线程

#include <iostream>
#include <thread>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
using namespace std;

void fun()
{
    while(true)
    {
        cout << "hello new thread" << endl;
        sleep(1);
    }
}

int main()
{
    std::thread t(fun);
    std::thread t1(fun);
    std::thread t2(fun);
    std::thread t3(fun);
    std::thread t4(fun);

    while(true)
    {
        cout << "hello main thread" << endl;
        sleep(1);
    }

    t.join();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值