第一集 C/C++多线程学习(奇迹行者每日一学!日更!如果没有更新请骂死我)

2023.9.4


1.概述

进程和线程:linux里的线程也属于进程

  1. 进程有有自己独立的地址空间,多个线程共用一个地址空间。
  2. 线程更加节省资源,效率不仅可以保持,而且更高。
  3. 在一个地址空间中多个线程共享:代码段,堆区,全局数据区,打开的文件(文件描述符)都是线程共享的。
  4. 线程是程序的最小执行单位,进程是操作系统中最小的资源分配单位。
  5. 每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片。
  6. 一个地址空间中可以划分出多个线程,在有效的资源基础上,能够抢更多的CPU时间片。
  7. CPU的调度和切换:线程的上下文切换比进程快的多。
  8. 线程更加廉价,启动速度快,退出也快,对系统资源冲击小。

2.创建线程

2.1获取当前线程函数

每一个线程都有一个唯一的线程ID。且ID 的类型为pthread_t,这个ID是一个无符号长整型数,如果想要得到当前线程ID,可以调用如下的函数:

pthread_t pthread_self(void); /*返回当前线程的线程id*/

2.2线程创建函数

在一个进程中调用线程创建函数,就可以得到一个子线程(原本的单进程变成了主线程),和进程不通,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。

pthread_create(pthread_t *thread, const pthread_attr_t *attr,
              void *(*start_routine)(void *), void *arg)
/*
    第一个参数是线程id的地址;
    第二个参数是线程的属性,一般情况下默认为NULL;
    第三个参数是函数指针(要执行的函数);
    第四个参数传递给函数的实参,如果要传递多个数据,则需要定义一个结构体来传递多个数据
*/

2.3代码案例:(主先子后原则)

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

void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
    return NULL;
}
int main()
{
    pthread_t tid;/*线程的id*/
/*第一个参数是线程id的地址;
    第二个参数是线程的属性,一般情况下默认为NULL;
    第三个参数是函数指针(要执行的函数);
    第四个参数传递给函数的实参,如果要传递多个数据,则需要定义一个结构体来传递多个数据*/
    pthread_create(&tid, NULL, callback, NULL);   
    /*编译的时候加-lpthread*/
    for(int i = 0; i < 5; i++)
    {
        printf("主线程:%d\n",i);
    }
    printf("主线程:%ld\n",pthread_self());
    printf("\n");
    return 0;
}

输出为:

或者:

每次运行程序都是不一样的,因为多线程是一个抢时间片的过程,主线程执行完了以后就会释放资源,其他的子线程就不继续执行了,所以我们需要想办法来解决问题,应该先子线程再进行主线程。

第一种方法:主线程先等一会,于是我们在代码中添加sleep(3)

    for(int i = 0; i < 5; i++)
    {
        printf("主线程:%d\n",i);
    }
    printf("主线程:%ld\n",pthread_self());
    sleep(3);

那么主线程抢完资源后陷入沉睡,子线程就会开始抢时间片,并执行。执行完后,主线程睡醒,然后程序结束(主线程比子线程先执行)。结果就会如下。


 

3.线程退出

编写多线程函数的时候,如果想要让线程退出,但不会导致虚拟内存地址空间的释放(即主线程还会继续运行,但是子线程运行中退出了),可以调用线程库中的线程退出函数,调用后立即退出当前线程,且不会影响其他线程的执行。子线程主线程都可以用。(但一般是用在主线程里,因为子线程结束也不会影响主线程,不会释放虚拟内存地址空间。而用在主线程里,让主线程结束,但是子线程还可以继续执行)

3.1线程退出函数

void pthread_exit(void *retval);
/*参数:线程退出的时候携带的数据,
当前子线程的主线程会得到该数据,如果不需要使用,就指定为NULL*/
/*一般是配合线程回收函数进行使用*/

3.2代码案例

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

/*这里如果void callback()的话,下面create函数第三个参数应该为(void*)callback*/
void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
        printf("\n");
    return NULL;
}
int main()
{
    pthread_t tid;/*线程的id*/
    pthread_create(&tid, NULL, callback, NULL);   
    printf("主线程:%ld\n",pthread_self());
    pthread_exit(NULL);
    return 0;
}

在主线程添加退出函数,可见主线程执行完毕后,子线程还会被执行。

4.线程回收

线程和进程一样,子线程退出的时候,其资源被主线程回收,而线程回收函数是一个阻塞函数。如果还有子线程在运行,调用该函数就会造成阻塞,子线程退出函数接触阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个则需要循环进行回收。

4.1线程回收函数

pthread_join()

int pthread_join(pthread_t thread, void **retval)
/*
第一个参数是要被回收的子线程
第二个参数是二级指针,指向一级指针的地址,是一个传出参数,存储了pthread_exit()传出
的数据,如果不需要就置NULL
*/

4.2代码案例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include <pthread.h>
struct Test
{
    int num;
    int age;
};

void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
    printf("\n");
    struct Test t;
    t.num = 100;
    t.age = 6;
    pthread_exit(&t);/*传出参数*/
    return NULL;/*退出线程,所以这句不能被执行了*/
}
int main()
{
    pthread_t tid;/*线程的id*/
    pthread_create(&tid, NULL, callback, NULL);   
    printf("主线程:%ld\n",pthread_self());
    
//   pthread_join(tid, NULL);/*不接收数据*/
 /*这是接收数据*/   
    void *ptr;/*定义一个指针, ptr指向t 的地址*/
    pthread_join(tid, &ptr);/*这里是&t(这也是地址)的地址,两级指针指向一级指针的地址*/
    struct Test* pt = (struct Test*)ptr;/*对void类型进行转换*/
    printf("num:%d,age:%d\n",pt->num,pt->age);
    return 0;
}

发现跟我们想的并不一样。这是为什么呢,因为这个地址是子线程的一块栈内存,一个虚拟地址空间只有一个栈,被多个线程瓜分,然后子线程用完,就会还回去,数据被释放。所以这个t变量在释放后就不复存在了。后面得到的只是一个随机数。

怎么保证是正确的?对了!保证地址不被释放,所以放在全局变量即可!

struct Test
{
    int num;
    int age;
};
struct Test t;
void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
    printf("\n");

    t.num = 100;
    t.age = 6;
    pthread_exit(&t);/*传出参数*/
    return NULL;/*退出线程,所以这句不能被执行了*/
}

这是第一种解决方案,第二种解决方案是使用主线程的栈空间。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include <pthread.h>
struct Test
{
    int num;
    int age;
};

void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
    printf("\n");

    struct Test *t = (struct Test*)arg;/*转换类型后,指针类型,需要用箭头指了*/
    t->num = 100;
    t->age = 6;
    pthread_exit(t);/*传出参数,此时不再是取地址*/
    return NULL;/*退出线程,所以这句不能被执行了*/
}
int main()
{
   struct Test t;/*需要把t的地址传到子线程。*/
    pthread_t tid;/*线程的id*/
    pthread_create(&tid, NULL, callback, &t);   
    printf("主线程:%ld\n",pthread_self());
    
//   pthread_join(tid, NULL);/*不接收数据*/
 /*这是接收数据*/   
    void *ptr;/*定义一个指针, ptr指向t 的地址*/

    pthread_join(tid, &ptr);/*这里是&t(这也是地址)的地址,两级指针指向一级指针的地址*/
    printf("num:%d,age:%d\n",t.num,t.age);
    return 0;
}

5.线程分离

5.1线程分离函数

某些情况下,程序中主线程也是有自己的业务处理流程,如果主线程负责子线程的资源回收,调用pthread_join()只要子线程不退出,主线程就会一直被阻塞,主要的任务也不能执行。

因此提供了pthread_detach(),调用这个函数之后指定的子线程可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了,线程分离后在主线程中用join函数就回收布道子线程资源了。

int pthread_detach(pthread_t thread);

当然当主线程执行完毕后(并非是线程退出),子线程也活不了,毕竟主线程都寄了,覆巢之下焉有完卵!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include <pthread.h>
struct Test
{
    int num;
    int age;
};

void* callback(void* arg)
{
    for(int i = 0; i < 5; i++)
    {
        printf("子线程:%d\n",i);
    }
    printf("子线程:%ld",pthread_self());
    printf("\n");

    pthread_exit(t);/*传出参数,此时不再是取地址,因为t就是指针类型,t就是地址*/
    return NULL;/*退出线程,所以这句不能被执行了*/
}
int main()
{
   struct Test t;/*需要把t的地址传到子线程。*/
    pthread_t tid;/*线程的id*/

    pthread_create(&tid, NULL, callback, &t);   
    printf("主线程:%ld\n",pthread_self());
    pthread_detach(tid);
    
    pthread_exit(NULL);/*退出不影响子线程*/


    return 0;
}

6.取消线程

满足条件就会杀死线程

int pthread_cancel(pthread_t thread);
/*调用成功返回0,失败返回非0*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值