05-系统编程(线程)

05-线程



一、linux最小资源单位 – 线程

1.线程与进程
进程: 进程是操作系统资源分配的基本实体
线程: 线程是CPU调度的基本单位,是进程内部资源,是linux中最小资源单位。(填空选择)
int main(int argc,char argv[])
{
//单进程
/
进程开始 */
…;
…; -> 在进程运行过程中,可以创建出多个线程。
…;

/* 进程结束 */
return 0;

}
假设linux是一个社会,进程就是一个车间,线程就是车间里面的各个工人。

2.线程函数接口特点?
1)由于线程函数接口都是封装在一个线程库,所以我们是看不到源码的,查看线程的函数,都是在第3手册: man 3 xxxx
2)所有线程函数的头文件:#include <pthread.h>

二、线程的函数接口

1.创建一个子线程 -> pthread_create()

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
函数参数:
thread:储存线程ID号的变量的地址 pthread_t -> 线程ID号的数据类型
attr:线程的属性,普通属性填NULL
start_routine:线程的执行函数。该线程需要做什么任务,就写到这个函数中函数必须是:void *fun(void *arg) 回调函数–>函数指针
arg:传递给子线程的参数(任意指针类型)
返回值:
成功:0
失败:非0错误码
注意:Compile and link with -pthread. 只要你的程序设计到线程的函数,在编译时必须链接线程库
编译命令: gcc xxx.c -o xxx -lpthread(编译线程的时候一定要加线程库)
说明:
1.在子线程理论上可以创建无数条子线程
2.主线程与子线程是并发运行,没有时间先后之分
3.主线程结束表示整个进程结束;包括子线程也结束。

  1. 函数指针与回调函数
    void *(*start_routine) (void *)
    它是一个函数指针类型,指针指向的是一个函数,函数的返回值是void *;函数的参数是void *
    typedef void (*sighandler_t)(int); //函数指针
    自定义了一个函数指针类型,指针指向一个函数,函数的返回值是void;函数的参数是int;
    给这个函数指针类型取了一个别名,名字叫做sighandler_t

代码解释:

#include <stdio.h>

int fun(int a, int b)
{
    printf("a+b=%d\n", a + b);
}

/*
    __LINE__:行号
    __FUNCTION__:函数名
*/
void *fun2(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);
    return NULL;
}

// 给自定义的函数指针类型取了一个别名 名字叫做bling
typedef int (*bling1)(int, int);

int main()
{
    /*
        //1.函数指针
        //2.指针指向的是一个函数
        //3.函数的参数是两个int类型
        //4.参数的返回值是一个int
    */

    // 给函数指针定义+初始化
    int (*bling)(int, int) = fun; // 常用的写法
    //  int (*bling)(int, int) = &fun;

    // 先定义再初始化
    //  int (*bling)(int, int);
    //  bling = fun;

    // 调用函数
    //  fun(3,4); //常规直接调用

    // 通过函数指针来调用函数
    bling(5, 6); // 常用的写法
    // (*bling)(5,6); //等价于(*&fun)(5,6)==fun(5,6)

    /*
        通过别名来定义函数指针变量
    */
    bling1 b1 = fun; // 常用写法
    b1(7, 8);

    // 非常用写法
    //  bling1 b1 = &fun;
    //  (*b1)(7,8);

    /*
        这种类型的函数指针
        void *(*bling2)(void *)
    */
    void *(*bling2)(void *) = fun2;
    // (void *)(*bling2)(void *) = fun2;  //不要给返回值加括号 错误
    bling2(NULL);

    return 0;
}

2)线程同时干不同的事
例子: 尝试创建一个新的线程,看看能不能同时做两件事情

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

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
    while(1)
    {
        sleep(1);
        printf("start_routine\n");
    }
    
} 
int main()
{
    //创建一条线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    while(1)
    {
        sleep(1);
        printf("main\n");
    }
    return 0;
}

验证一个进程最多能创建几条线程:

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

/*
    单进程
*/

//子线程start_routine1
void *start_routine1(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    while(1)
    {
        printf("我爱坤坤.....\n");
        sleep(1);
    }
}

//子线程start_routine2
void *start_routine2(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    while(1)
    {
        printf("我是ikun.....\n");
        sleep(1);
    }
}

int main()
{
    /*
        主线程
    */

    //创建一个子线程
    int ret = 0;
    pthread_t tid; //不要定义成pthread_t *tid
    ret = pthread_create(&tid,NULL,start_routine1,NULL);
    if(ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    //创建一个子线程
    pthread_t tid1; //不要定义成pthread_t *tid
    ret = pthread_create(&tid1,NULL,start_routine2,NULL);
    if(ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    //普通的函数调用
    // start_routine2(NULL);

    // pause();
    while(1)
    {
        printf("坤坤打篮球.....\n");
        sleep(1);     
    }

    return 0;
}

3)线程的传参
struct st
{
char name[20];
char sex;
int height;
float score;
}
使用线程将一个学生信息传递给子线程,然后在子线程中打印该学生的信息。

代码说明:

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

// 子线程fun1   ---char类型
void *fun1(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    // 拿传参的值
    // 1.类型转换
    // 2.解引用
    char c = *((char *)arg);
    printf("arg char:%c\n", c);
}

// 子线程fun2   ---- int类型
void *fun2(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    // 拿传参的值
    // 1.类型转换
    // 2.解引用
    int num = *(int *)arg;
    printf("arg int:%d\n", num);
}

// 子线程fun3--- int *指针
void *fun3(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    // 拿传参的值
    // 1.类型转换
    // 2.解引用
    int num = *(int *)arg;
    printf("arg int *:%d\n", num);
}

// 子线程fun4----char * 字符串
void *fun4(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    // 拿传参的值
    // 1.类型转换
    // 2.解引用
    printf("arg char *:%s\n", (char *)arg);
}

// 子线程fun5-----int arr[2]
void *fun5(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    // 拿传参的值
    // 1.类型转换
    // 2.解引用
    int *arr = (int *)arg;

    // printf("arg int [2]:%d %d\n",*arr,*(arr+1));
    printf("arg int [2]:%d %d\n", arr[0], arr[1]);
}

int main()
{

    int ret = 0;
    /*
        传参 :char类型
    */
    char c = 'a';
    pthread_t tid1;
    //&c--> char * 类型转换
    ret = pthread_create(&tid1, NULL, fun1, (void *)&c); // 刚开始的时候建议这么写
    // ret = pthread_create(&tid1,NULL,fun1,&c); //char *是void *类型中一种
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    /*
        传参 :int类型
    */
    int num = 250;
    pthread_t tid2;
    //&num--> int * 类型转换
    ret = pthread_create(&tid2, NULL, fun2, (void *)&num); // 刚开始的时候建议这么写
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    /*
        传参 :指针类型
    */
    int val = 300;
    // int *p = &val; //指针p获得是栈空间
    int *p = malloc(sizeof(int)); // 堆空间
    *p = 350;
    pthread_t tid3;
    ret = pthread_create(&tid3, NULL, fun3, (void *)p); // 刚开始的时候建议这么写
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    /*
        传参 :字符串类型 char *
    */
    char *str = "合班6 牛逼";
    pthread_t tid4;
    ret = pthread_create(&tid4, NULL, fun4, (void *)str);
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    /*
        传参:数组
    */
    int arr[2] = {3, 4};
    pthread_t tid5;
    ret = pthread_create(&tid5, NULL, fun5, (void *)arr);
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    // while (1);
    pthread_jpin(tid1, NULL);
    pthread_jpin(tid2, NULL);
    pthread_jpin(tid3, NULL);
    pthread_jpin(tid4, NULL);
    pthread_jpin(tid5, NULL);

    return 0;
}

2、接合一条线程 (阻塞等待子线程的退出) -> pthread_join()

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
thread:需要接合的线程的ID号
retval:储存子线程的退出值指针,如果填NULL,代表不关心子线程的退出状态。
返回值:
成功:0
失败:非0

例子:

情况一:
主线程工作时间长,才去join()。 -> pthread_join()会马上返回。
子线程工作时间短,早就结束。
情况二:
主线程工作时间短,早去join()。 -> pthread_join()会阻塞等待子线程的退出。
子线程工作时间长。

代码参考:

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

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
    sleep(5);
    printf("start_routine\n");  
} 

int main()
{
    //创建一条线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    //sleep(1);
    printf("main\n");
    
    //等待子线程退出
    pthread_join(thread,NULL);
    
    return 0;
}

说明:
printf(“[%s][%d]num = %d\n”,FUNCTION,LINE,*((int *)arg));
利用__FUNCTION__和__LINE__可以区分子线程和主线程的打印

代码说明:

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

/*
    单进程
*/

//子线程start_routine1
void *start_routine1(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    int cnt = 10;
    while(cnt--)
    {
        printf("我爱坤坤.....%d\n",cnt);
        sleep(1);     
    }

}

int main()
{
    /*
        主线程
    */

    //创建一个子线程
    int ret = 0;
    pthread_t tid; //不要定义成pthread_t *tid
    ret = pthread_create(&tid,NULL,start_routine1,NULL);
    if(ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    int cnt = 5;
    while(cnt--)
    {
        printf("我是ikun.....%d\n",cnt);
        sleep(1);     
    }

    //主线程结合子线程(阻塞等待子线程的退出)
    ret = pthread_join(tid,NULL); //类比wait(NULL)
    if(ret != 0)
    {
        perror("pthread_join fail");
    }
    perror("pthread_join success");

    return 0;
}

3、退出线程 -> pthread_exit()

#include <pthread.h>
void pthread_exit(void *retval);
函数作用:
子线程主动结束退出
参数:
retval:子线程退出值变量的地址 -> 这个退出值必须是全局变量(或者是堆)
说明:
不能将遗言存放到线程的局部变量里,因为如果用户写的线程函数退出了,
线程函数栈上的局部变量可能就不复存在了。(函数结束栈空间)

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

int pthread_status = 10;//线程的退出变量 必须存在数据段中

//线程例程
void* start_routine(void*arg)
{
    /* int cnt=10;
    while(cnt--)
    {
        sleep(1);
        printf("start_routine arg:%d cnt:%d\n",*((int*)arg),cnt);
    } */
    printf("start_routine\n");
    //子线程主动退出
    pthread_exit(&pthread_status);

    printf("3333\n");
}

int main()
{
    //只有一条(PCB结构体只有一个)线程的主进程 
    //主线程 
    printf("main start.....\n");
    int val = 100;
    int ret;
    //创建一条子线程(申请一个内核 PCB任务结构体),执行任务(线程例程,说白了创建这条线程之后,去执行一个函数)
    pthread_t thread;      
    ret = pthread_create(&thread,NULL,start_routine, &val);
    if(ret != 0){
        printf("pthread_create error\n");
    }

    /* int cnt=20;
    while(cnt--){
    sleep(1);
    printf("主线程 正在执行任务%d....\n",cnt);
    } */

    //阻塞等待子进程的退出
    pthread_join(thread,NULL);
    void *retval = NULL; //void *retval = &pthread_status;//重点写法
    pthread_join(thread,&retval); //pthread_join(pthread_tthread,void**retval);
    printf("阻塞等待子线程[%lu]的退出 成功 退出值为:%d\n",thread,*(int*)retval);

    //进程结束
    printf("main end.......\n");
    return 0;
}
导致线程退出的因素:
 The new thread terminates in one of the following ways:
//以下这几种情况之一都可以导致线程退出
       * It calls pthread_exit(3), specifying an exit status value that is available to another thread in the  same  process  that  calls pthread_join(3).
//1)当线程调用了pthread_exit(),还可以将退出值返回给那个接合的它的线程。
       * It returns from start_routine().  This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
//2)当例程函数返回时,也可以导致线程的退出,return后面的值就是退出值。
       * It is canceled (see pthread_cancel(3)).
//3)收到取消请求。
       * Any  of the threads in the process calls exit(3), or the main thread performs a return from main().  This causes the termination of all threads in the process.
//4)任意一个线程调用exit(),都会导致所有的线程都退出/main函数返回

代码说明:

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

/*
    单进程
*/

int val = 250; // 全局变量---数据段中 ok

// 子线程start_routine1
void *start_routine1(void *arg)
{
    // int val = 250; //占空间不ok

    // int *p = malloc(4); //堆空间也ok
    // *p = 250;
    // pthread_exit((void *)p); 

    printf("[%d][%s]\n", __LINE__, __FUNCTION__);

    int cnt = 1;
    while (cnt--)
    {
        printf("我爱坤坤.....%d\n", cnt);
        sleep(1);
    }

    // 线程的退出(如果不需要子线程的退出值,可以不写)
    pthread_exit(NULL);  //常用写法

    // 退出的全局变量
    // pthread_exit((void *)&val); //&val-->int *

    // 子线程退出一个自己的值"886"
    //"886"字符串常量--->.raodata----->数据段
    //pthread_exit((void *)"886"); // char *类型

    // 子线程退出一个自己的值"1" 模拟return 1的写法
    //"1"字符串常量--->.raodata----->数据段
    // pthread_exit((void *)"1"); // char *类型

    // exit(0); //慎用
}

int main()
{
    /*
        主线程
    */

    // 创建一个子线程
    int ret = 0;
    pthread_t tid; // 不要定义成pthread_t *tid
    ret = pthread_create(&tid, NULL, start_routine1, NULL);
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    int cnt = 1;
    while (cnt--)
    {
        printf("我是ikun.....%d\n", cnt);
        sleep(1);
    }

    // 主线程结合子线程不关心子线程的退出值(阻塞等待子线程的退出)
    //  ret = pthread_join(tid,NULL); //类比wait(NULL) 常用写法
    //  if(ret != 0)
    //  {
    //      perror("pthread_join fail");
    //  }
    //  perror("pthread_join success");

    // int pthread_join(pthread_t thread, void **retval);
    // 主线程结合子线程关心子线程的退出值(阻塞等待子线程的退出)

    void *retval = NULL; // 不要定义成void **retval
    //&retval的类型就是void **类型
    ret = pthread_join(tid, &retval); // retval指针是通过函数pthread_join给它空间
    if (ret != 0)
    {
        perror("pthread_join fail");
    }
    // 此处接收的是全变变量 int val=250
    printf("pthread_join success retval:%d\n",*(int*)retval); //如果retval没有空间的一定会段错误(段错误的原因就是访问空指针)

    // 此处接收的是常量"886"
    // printf("pthread_join success retval:%s\n", (char *)retval);

    //此处接收的是常量"1"
    // int ret1 = atoi((char *)retval);
    // printf("pthread_join success retval:%d\n", atoi((char *)retval) );
    // if(ret1 == 1)
    // {
    //     printf("ok\n");
    // }
    // if(strcmp((char *)retval,"1") == 0)
    // {
    //     printf("ook\n");
    // }


    return 0;
}

三、主线程与子线程之间的通信方式

主线程与子线程之间互相通信有哪几种方式:
1.主线程通过参数arg来给子线程发送数据
2.子线程用pthread_exit()退出,主线程用pthread_join()来接受子线程的值
3.全局变量

代码说明:

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

/*
    单进程
*/

int val = 250; // 全局变量---数据段中 ok

// 子线程start_routine1
void *start_routine1(void *arg)
{
    printf("[%d][%s]\n", __LINE__, __FUNCTION__);
    int num = *(int *)arg;

    // 子线程退出一个自己的值"1" 模拟return 1的写法
    //"1"字符串常量--->.raodata----->数据段
    pthread_exit((void *)"1"); // char *类型

}

int main()
{
    /*
        主线程
    */

    // 创建一个子线程
    int num = 10;

    int ret = 0;
    pthread_t tid; // 不要定义成pthread_t *tid
    ret = pthread_create(&tid, NULL, start_routine1, (void *)&num);
    if (ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    //结合子线程
    void *retval = NULL; // 不要定义成void **retval
    //&retval的类型就是void **类型
    ret = pthread_join(tid, &retval); // retval指针是通过函数pthread_join给它空间
    if (ret != 0)
    {
        perror("pthread_join fail");
    }
    // 此处接收的是全变变量 int val=250
    printf("pthread_join success retval:%d\n",*(int*)retval); //如果retval没有空间的一定会段错误(段错误的原因就是访问空指针)


    return 0;
}

总结

没有总结…下篇继续…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值