Linux高级编程——线程

pthread 线程

概念 :线程是轻量级进程,一般是一个进程中的多个任务。

进程是系统中最小的资源分配单位.

线程是系统中最小的执行单位。

优点: 比多进程节省资源,可以共享变量

进程会占用3g左右的空间,线程只会占用一部分,大概8M的空间

进程的父子不会共享,但一个进程之间的线程的资源可以共享.

进程的父子不是平级关系,线程是平级关系

特征:s's

1、共享资源

2、效率高 30%

3、三方库: pthread clone posix

3.1 编写代码头文件: pthread.h

3.2 编译代码加载库: -lpthread library

libpthread.so (linux库)

gcc 1.c -lpthread -lc
缺点:

1,线程和进程相比,稳定性,稍微差些

2,线程的调试gdb,相对麻烦些。

info thread

*1

2

3

thread 3

线程与进程区别:

资源:

线程比进程多了共享资源。 IPC

线程又具有部分私有资源。

进程间只有私有资源没有共享资源。

空间:

进程空间独立,不能直接通信。

线程可以共享空间,可以直接通信。

进程解决相对复杂的问题,线 程解决相对复杂的问题.

共同点:

二者都可以并发

3、线程的设计框架 posix

创建多线程 ==》线程空间操作 ===》线程资源回收

errno strerror(errno) perror();

3.1 创建多线程:

int pthread_create(

pthread_t *thread , const pthread_attr_t *attr,

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

功能:该函数可以创建指定的一个线程。

参数:thread 线程id,需要实现定义并由该函数返回。

attr 线程属性,一般是NULL,表示默认属性。

start_routine 指向指针函数的函数指针。

本质上是一个函数的名称即可。称为

th 回调函数,是线程的执行空间。

{

}

arg 回调函数的参数,即参数3的指针函数参数。

返回值:成功 0

失败 错误码

注意:一次pthread_create执行只能创建一个线程。

每个进程至少有一个线程称为主线程。

主线程退出则所有创建的子线程都退出。暂时先用while(1);

主线程必须有子线程同时运行才算多线程程序。

线程id是线程的唯一标识,是CPU维护的一组数字。

pstree 查看系统中多线程的对应关系。

多个子线程可以执行同一回调函数。

ps -eLf 查看线程相关信息Low Weigth Process
ps -eLo pid,ppid,lwp,stat,comm

cs 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *th1(void*arg)
{
	while(1)
	{

	printf("发送视频\n");
	sleep(1);
	}
}

void *th2(void*arg)
{
	while(1)
	{
		printf("接受控制\n");
	}
}

int main(int argc, const char *argv[])
{
	pthread_t tid1,tid2;
	pthread_create(&tid1,NULL,th1,NULL);
	pthread_create(&tid2,NULL,th2,NULL);
	while(1);

	return 0;
}
  1. main 函数开始执行。
  2. 使用 pthread_create 创建了两个线程 tid1tid2
  3. th1 线程开始执行其无限循环,并在每次迭代中打印 "发送视频",然后暂停一秒。
  4. 同时(几乎是同时),th2 线程也开始执行其无限循环,不断打印 "接受控制"。
  5. 因为两个线程是并发执行的,所以它们之间没有固定的打印顺序。这取决于操作系统调度器的决策,哪个线程在何时获得CPU时间片。
  6. main 函数中的 while(1); 是一个空循环,它使主线程保持活动状态,防止程序立即退出。然而,这个空循环并没有为程序提供任何有用的功能,通常你可能会使用某种形式的线程同步或等待(如 pthread_join)来确保主线程在所有其他线程完成后才退出。

此时输出是乱的,是由于

  • 线程调度是由操作系统控制的,它决定哪个线程在何时运行。这取决于许多因素,包括线程优先级、系统负载、可用的CPU核心数量等。
  • 由于两个线程都在无限循环中,并且没有同步机制(如互斥锁、条件变量等),所以它们会尽可能快地交替执行(或并行执行,如果系统有多个CPU核心),导致输出看起来没有规律。

2、pthread_t pthread_self(void); unsigned long int; %lu 获取线程号

功能:获取当前线程的线程id

参数:无

返回值:成功 返回当前线程的线程id

失败 -1;

syscall(SYS_gettid);

这个方法重启后失效

alias gcc='gcc -g -pthread '

unalias gcc

永久起作用

cd ~ //家目录

vim .bashrc

alias gcc='gcc -g -pthread ' :wq

source .bashrc 生效

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *th1 (void*arg)
{
    while(1)
    {
        printf("发送视频 %lu\n",pthread_self());
        sleep(1);
    }
}

void *th2 (void*arg)
{
    while(1)
    {
        printf("接受控制 %lu\n",pthread_self());
        sleep(1);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,th1,NULL);
    pthread_create(&tid2,NULL,th2,NULL);
    printf("main th %lu\n",pthread_self());
    while(1);
    return 0;
}
  • 使用pthread_create创建两个线程:tid1(运行th1)和tid2(运行th2)。
  • 打印主线程的ID。
  • 使用while(1);使主线程进入无限循环,以保持程序运行。否则,当主线程结束时,程序可能会立即终止,导致其他线程也被终止

练习题:

设计一个多线程程序,至少有三个子线程

每个线程执行不同的任务,并实时打印执行

过程,同时表明身份。

eg: ./a.out ==>tid =xxx... zheng ...

tid2 = xxx wozai.

tid3 = xxx wozai ssss

线程的退出:

1.直接用return;

2: 自行退出 ==》自杀 ==》子线程自己退出

exit(1);

void pthread_exit(void *retval); exit return p;

功能:子线程自行退出

参数: retval 线程退出时候的返回状态,临死遗言。

返回值:无

th

{

int a =10;

pthread_exit(&a);

}

join(,&ret)

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *th1 (void*arg)
{
    int i =10;
    while(i--)
    {
        printf("发送视频 %lu\n",pthread_self());
        sleep(1);
    }
    pthread_exit(NULL);//return NULL;
}

void *th2 (void*arg)
{
    int i = 10;
    while(i--)
    {
        printf("接受控制 %lu\n",pthread_self());
        sleep(1);
    }
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,th1,NULL);
    pthread_create(&tid2,NULL,th2,NULL);
    printf("main th %lu\n",pthread_self());
    while(1);
    return 0;
}
  1. 强制退出 ==》他杀 ==》主线程结束子线程

int pthread_cancel(pthread_t thread);

功能:请求结束一个线程 (在主线程种调用 写入某个线程id号,可以关闭该线程)

参数:thread 请求结束一个线程tid(想要关闭的线程id号)

返回值:成功 0

失败 -1;

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *th1 (void*arg)
{
    while(1)
    {
        printf("发送视频\n");
        sleep(1);
    }
}

void *th2 (void*arg)
{
    while(1)
    {
        printf("接受控制\n");
        sleep(1);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,th1,NULL);
    pthread_create(&tid2,NULL,th2,NULL);
    int i = 0 ;
    while(1)
    {

        i++;
        if(3 == i )
        {
            pthread_cancel(tid1);
        }
        
        if(5 ==i)
        {
        
            pthread_cancel(tid2);
        }
        sleep(1);
    }
    return 0;
}

作业:

创建一个多线程程序,至少有10个子线程,

每个线程有会打印不同的数据,同时表明身份。

线程的回收

1、线程的回收机制 ====》不同与进程没有孤儿线程和僵尸线程。

====》主线程结束任意生成的子线程都会结束。

====》 子线程的结束不会影响主线程的运行。

char * retval ; retval++; 1

int * retval;

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

功能:通过该函数可以将指定的线程资源回收,该函数具有阻塞等待功能,如果指定的线程没有结束,则回收线程会阻塞。

参数:thread 要回收的子线程tid

retval 要回收的子线程返回值/状态。==》ptread_exit(值);

返回值:成功 0

失败 返回一个错误号,是一个大于零的数;

失败可以用

cs 复制代码
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *th1 (void*arg)
{
    int i = 10;
    while(i--)
    {
        printf("发送视频\n");
        sleep(1);
    }
}

void *th2 (void*arg)
{
    int i = 10;
    while(i--)
    {
        printf("接受控制\n");
        sleep(1);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,th1,NULL);
    int ret = pthread_create(&tid2,NULL,th2,NULL);
    if(ret!=0)
    {
       // perror()
       fprintf(stderr,"error %s\n",strerror(ret));//exit();
    }
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    return 0;
}

子线程的回收策略:

1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。

2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收。

3、如果子线程已知必须长时间运行则,不再回收其资源。

线程的参数,返回值

1、传参数

传整数 ===》int add(int a,int b); ///a b 形参

add(x,y); x y 实参

pthread_create(&tid,NULL,fun,x);

fun ==>void * fun(void * arg);

练习:创建一个子线程并向该线程中传入一个字符在

线程中打印输出。

在此基础上向子线程中传入一个字符串,并在

子线程中打印输出。

add(int a, int b)

{

int c = a+b;

char buf[]=""

return c;

}

5

int d = add(2,3);

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* th(void* arg)
{
    static int a =20;
    return &a;
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    void* ret;
    pthread_create(&tid,NULL,th,NULL);
    pthread_join(tid,&ret);
    printf("ret %d\n",*(int*)ret);
    return 0;
}

传字符串

栈区字符数组:

字符串常量:

char *p = "hello";

堆区字符串;

char *pc = (char *)malloc(128);

ptread_create(&tid,NULL,fun,pc);

pthread_join(tid,NULL);

free(pc);

fun(void *arg)

{

char * pc = (char *)arg ;

printf("%s \n",pc);

%c

}

栈区

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* th(void* arg)
{
    static char buf[256]={0};
    strcpy(buf,"要消亡了\n");
    return buf;
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    void* ret;
    pthread_create(&tid,NULL,th,NULL);
    pthread_join(tid,&ret);
    printf("ret %s\n",(char*)ret);
    return 0;
}

堆区:

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* th(void* arg)
{
    char * tmp = (char* )arg;
    strcpy(tmp,"hello");
    return tmp;
}

int main(int argc, char *argv[])
{
    pthread_t tid;

    char * p = (char*)malloc(50);
    void* ret;
    pthread_create(&tid,NULL,th,p);
    pthread_join(tid,&ret);
    printf("ret %s\n",(char*)ret);
    free(p);
    return 0;
}

传结构体

1、定义结构体类型

2、用结构体定义变量

3、向pthread_create传结构体变量

4、从fun子线程中获取结构体数据

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
typedef struct 
{
    char * p;
    int a;
}TH_ARG;
void* th(void* arg)
{
    TH_ARG * tmp = (TH_ARG* )arg;
    strcpy(tmp->p,"hello");
    //strcpy( ((TH_ARG*)arg)->p ,"hello");
    tmp->a +=10;
    return tmp;
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    int a  =20;
    char * p = (char*)malloc(50);
    TH_ARG arg;
    arg.a = a;
    arg.p = p;
    void* ret;
    pthread_create(&tid,NULL,th,&arg);
    pthread_join(tid,&ret);
    printf("ret %s %d\n",((TH_ARG*)ret)->p,((TH_ARG*)ret)->a);
    free(p);
    return 0;
}

练习:

定义一个包含不同数据类型的测试结构体

并向子线程传参数,同时在子线程中打印输出。

定义一个回调函数可以完成计算器的功能

定义一个数据结构体可以一次传入不同的数据

和计算方式并将结果打印输出。

//2 + 3.6

// 2 + 3 2+3

// 8 * 6

typedef strcut

{

float a;

float b;

char c;//+ - * /

float d;

}JSQ;

返回值:pthread_exit(0) ===>pthread_exit(9);

pthread_join(tid,NULL); ===>pthread_join(tid,?);

10;

-10;

int * p =malloc(4);

*p = -10;

1、pthread_exit(?) ==>? = void * retval;

纯地址

2、pthread_join(tid,?) ==>? = void **retval;

地址的地址

原理:子线程退出的时候,可以返回一个内存地址

改值所在的内存中可以存储任何数据,只要

地址存在,则数据都可以正常返回。

地址有三种:

0、栈区变量 错误,子线程结束该地址失效。

1、全局变量 失去意义,本质可以直接访问。

2、静态变量

3、堆区变量

主线程通过一个地址形式的变量来接受子进程

返回的地址变量就可以将该地址中的数据取到。

练习:从子线程中申请一块堆区内存并存字符串

将该字符串以返回值形式返回到主线程并打印输出。

设置分离属性,目的线程消亡,自动回收空间。

主线程没有空,才设置分离属性来回收.

attribute

int pthread_attr_init(pthread_attr_t *attr) ;

功能,初始化一个attr的变量

参数:attr,需要变量来接受初始值

返回:0 成功,

非0 错误;

int pthread_attr_destroy(pthread_attr_t *attr);

功能:销毁attr变量。

attr,属性变量

返回:0 成功,

非0 错误;

man -k

int pthread_attr_setdetachstate(pthread_attr_t *attr
, int detachstate);

功能:把一个线程设置成相应的属性

参数,attr,属性变量,有init函数初始化他。

detachstate:有2个可选值,

PTHREAD_CREATE_DETACHED:设置分离属性。

第二种设置分离属性:
int pthread_deatch(pthread_t thread);

功能,设置分离属性

参数,线程id号,填自己的id

do{

}while()

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* th(void* arg)
{
    pthread_detach(pthread_self());
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    int i = 0 ;
    for(i=0;i<50000;i++)
    {
        int ret = pthread_create(&tid,NULL,th,NULL);
        if(ret!=0)
        {
            break;
        }
       // pthread_detach(tid);
    }
    printf("%d \n",i);
    return 0;
}

void pthread_cleanup_push(void (*routine)(void *), void *arg);

功能:注册一个线程清理函数

参数,routine,线程清理函数的入口

arg,清理函数的参数。

返回值,无

void pthread_cleanup_pop(int execute);

功能:调用清理函数

execute,非0 执行清理函数

0 ,不执行清理

返回值,无

do

{

}while(1)

process thread

fork pthread_create

getpid,ppid, pthread_self

exit, pthread_exit

wait,waitpid, pthread_join

kill, pthread_cancel

atexit pthread_clean,

exec system--->fork->exec (ls)

相关推荐
起名字真南5 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
爬山算法10 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
tyler_download16 分钟前
golang 实现比特币内核:实现基于椭圆曲线的数字签名和验证
开发语言·数据库·golang
小小小~16 分钟前
qt5将程序打包并使用
开发语言·qt
hlsd#17 分钟前
go mod 依赖管理
开发语言·后端·golang
小春学渗透18 分钟前
Day107:代码审计-PHP模型开发篇&MVC层&RCE执行&文件对比法&1day分析&0day验证
开发语言·安全·web安全·php·mvc
杜杜的man21 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
亦世凡华、21 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
2401_8574396934 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66635 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节