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;
}
main
函数开始执行。- 使用
pthread_create
创建了两个线程tid1
和tid2
。 th1
线程开始执行其无限循环,并在每次迭代中打印 "发送视频",然后暂停一秒。- 同时(几乎是同时),
th2
线程也开始执行其无限循环,不断打印 "接受控制"。 - 因为两个线程是并发执行的,所以它们之间没有固定的打印顺序。这取决于操作系统调度器的决策,哪个线程在何时获得CPU时间片。
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;
}
- 强制退出 ==》他杀 ==》主线程结束子线程
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)