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)

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔8 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说13 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json