J1.背景:为什么会用到锁?
实际编程中,会出现:一个进程里,主线程会和其他多个子线程会共享临界资源(比如全局变量,全局结构等),访问资源,他们都可以修改共享的临界资源里的数据,而且运行是无序,所以就很容易产生脏数据,使数据破坏,也可能一个数据被不同的线程或进程在同一时间进行访问操作等,此时数据就很容易紊乱变成脏数据,那么怎么解决?那就是在某个线程或者进程操作临界资源的时候,此时不允许任何线程/进程访问操作此数据,等此线程对数据操作完毕后,再允许使其他线程操作(暂时称为解决方案a), 这样就不会导致程序的结果产生二义性
临界资源:在同一时刻,该资源只能被一个线程(执行流所访问)
访问:在临界区当中对临界资源进行非原子操作
J2.转化理解锁
针对J1的问题,我们已经找到了解决方案a,那么把这种思想再转化下:就像一个房子里有一个临界数据,很多线程都可以访问,但是为了安全和数据干净,我们都给有资格进入(访问)这个房子(临界资源)的人(线程)配了一把锁和钥匙,每个人进去后,必须给房子上锁,上锁后,其他人都无法再进入这个房子,此时,只能是进入房子的人对房子里的数据进行唯一的、无二议性的访问,其他想访问的人,只能等待:等房子里的人访问完数据,解锁出来后,其他人再进入房子,给房子加锁,访问,解锁...(请深入理解这个过程)
J3.抽象代码化
仔细理解和思索J2的举例,那么我们不难分析出这样一个逻辑循环:进房子->上锁->访问数据->解锁->出房子......
那么再把这个抽象整理下,比如说线程访问数据呢?那就是抽象如下:线程有访问数据的需求->加锁->访问数据->解锁->访问结束
再精华具体:访问临界资源的需求->加锁->访问临界资源->解锁->访问结束(销毁锁)
两个概念的理解:
【互斥】
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
【同步】
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。
同步就是在互斥的基础上有顺序
其实,简单的总结下就是:互斥是为了进程访问数据时的唯一性和排它性,直白点就是说:一个进程访问这个数据时,其他所有进程没有资格干预,这也就保证了同一时间内只有一个进程访问这个数据,那么肯定不会产生二义性,也不会使数据成为脏数据;同步说白了就是在互斥的基础上,多个进程有先后次序的对数据进行访问。
J4. 代码举例
001.进程的创建
#include <stdio.h>
#include <string.h>
#include <pthread.h>
/*****************************************************
*线程操作:单线程、多线程操作
* **************************************************/
char *str = "Hellopthread";
void* funcp(void* arg)
{
long tid;
tid = pthread_self();//获取当前线程的id
printf("当前线程id: %ld ===== %ld\n", tid, pthread_self());
printf("正在执行此线程=====================================\n");
printf("=======%s====\n", (char*)arg);
return NULL;
}
void operation()
{
pthread_t Tid; //1.定义一个线程
pthread_create(&Tid, NULL, funcp, (void*)str); //2.创建线程并执行函数funcp
pthread_join(Tid, NULL); //3.主线程等待Tid线程执行完后,然后主线程继续往下执行
printf("thread Tid has finished!!=======\n");
printf("Tid = %ld\n", Tid);
printf("回到主线程==========完毕=======\n");
}
int main () {
operation();
return 0;
}
执行结果:
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140413483407104 ===== 140413483407104
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140413483407104
回到主线程==========完毕=======
002.加锁不释放锁,看看结果
/*****************************************************
*线程操作:单线程、多线程操作
* **************************************************/
char *str = "Hellopthread";
void* funcp(void* arg)
{
long tid;
tid = pthread_self();//获取当前线程的id
printf("当前线程id: %ld ===== %ld\n", tid, pthread_self());
printf("正在执行此线程=====================================\n");
printf("=======%s====\n", (char*)arg);
return NULL;
}
void operation()
{
pthread_t Tid; //1.定义一个线程
pthread_create(&Tid, NULL, funcp, (void*)str); //2.创建线程并执行函数funcp
pthread_join(Tid, NULL); //3.主线程等待Tid线程执行完后,然后主线程继续往下执行
printf("thread Tid has finished!!=======\n");
printf("Tid = %ld\n", Tid);
printf("回到主线程==========完毕=======\n");
}
/*全局变量*/
int global_num = 10;
pthread_mutex_t mlock;
char *afstr = "it is af_func operation.....";
char *aestr = "it is ae_func operation.....";
void* af(void *arg)
{
printf("%s\n", (char*)arg);
pthread_mutex_lock(&mlock);//加锁
global_num++; //访问临界资源
printf("---------global_num = %d--------\n", global_num);
printf("af operation funished!\n");
}
void* ae(void* arg)
{
printf("%s\n", (char*)arg);
global_num = 20;
printf("---------global_num = %d--------\n", global_num);
printf("ae operation funished!\n");
}
int add_suo()
{
pthread_t A_tid;//定义线程A
pthread_t B_tid;//定义线程B
printf("---------global_num = %d--------\n", global_num);
pthread_mutex_init(&mlock, NULL);//初始化锁
pthread_create(&A_tid, NULL, af, (void*)afstr);//创建线程A
//pthread_join(&A_tid, NULL);//等待线程A执行完毕
printf("---------global_num = %d--------\n", global_num);
pthread_create(&B_tid, NULL, ae, (void*)aestr);//创建线程B
printf("---------global_num = %d--------\n", global_num);
pthread_mutex_destroy(&mlock);//销毁锁
}
int main () {
operation();
printf("#######################################\n");
add_suo();
printf("----main-----global_num = %d--------\n", global_num);
return 0;
}
执行结果如下:
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 139724250568448 ===== 139724250568448
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 139724250568448
回到主线程==========完毕=======
#######################################
---------global_num = 10--------
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
---------global_num = 10--------
----main-----global_num = 11--------
it is ae_func operation.....
it is ae_func operation.....
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140597408634624 ===== 140597408634624
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140597408634624
回到主线程==========完毕=======
#######################################
---------global_num = 10--------
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
---------global_num = 11--------
----main-----global_num = 11--------
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140516890810112 ===== 140516890810112
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140516890810112
回到主线程==========完毕=======
#######################################
---------global_num = 10--------
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
it is ae_func operation.....
---------global_num = 20--------
ae operation funished!
---------global_num = 10--------
----main-----global_num = 20--------
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140551914514176 ===== 140551914514176
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140551914514176
回到主线程==========完毕=======
#######################################
---------global_num = 10--------
---------global_num = 10--------
---------global_num = 10--------
----main-----global_num = 10--------
it is af_func operation.....
it is af_func operation.....
【分析】
通过执行./edit程序,我们可以发现,每次都有不同的结果,对于全局变量global_num,为什么会这样呢?
原因如下:
1.线程对数据的访问顺序,并不是像c语言自上向下的执行顺序二进行的,所以,你会看到,有时候线程B能操作全局变量global_num,有时候却操作不了,如果不是线程,是两个函数的话,对数据的访问肯定是:哪个函数在前先调用的,哪个函数先访问数据,但是线程不是这样的
2.因为线程A已经对全局变量global_num加锁了,所以只有它自己可以访问这个数据,直到它释放锁后,其他线程才可以访问global_num,所以当线程A先访问global_num时,由于加了锁,却没有解锁,所以线程B在访问global_num的时候,就肯定访问不到,处于阻塞态
总结以上两点,所以每次执行./edit时,就会有不同的结果,这就是根因
下面我们正常合理的使用锁,看下效果:
003.正常用锁
/*****************************************************
*线程操作:单线程、多线程操作
* **************************************************/
char *str = "Hellopthread";
void* funcp(void* arg)
{
long tid;
tid = pthread_self();//获取当前线程的id
printf("当前线程id: %ld ===== %ld\n", tid, pthread_self());
printf("正在执行此线程=====================================\n");
printf("=======%s====\n", (char*)arg);
return NULL;
}
void operation()
{
pthread_t Tid; //1.定义一个线程
pthread_create(&Tid, NULL, funcp, (void*)str); //2.创建线程并执行函数funcp
pthread_join(Tid, NULL); //3.主线程等待Tid线程执行完后,然后主线程继续往下执行
printf("thread Tid has finished!!=======\n");
printf("Tid = %ld\n", Tid);
printf("回到主线程==========完毕=======\n");
}
/*全局变量*/
int global_num = 10;
pthread_mutex_t mlock;
char *afstr = "it is af_func operation.....";
char *aestr = "it is ae_func operation.....";
void* af(void *arg)
{
printf("%s\n", (char*)arg);
pthread_mutex_lock(&mlock); //加锁
global_num++; //访问临界资源
printf("---------global_num = %d--------\n", global_num);
printf("af operation funished!\n");
pthread_mutex_unlock(&mlock); //解锁
}
void* ae(void* arg)
{
printf("%s\n", (char*)arg);
pthread_mutex_lock(&mlock);
global_num = 20;
printf("---------global_num = %d--------\n", global_num);
printf("ae operation funished!\n");
pthread_mutex_unlock(&mlock);
}
int add_suo()
{
pthread_t A_tid; //定义线程A
pthread_t B_tid; //定义线程B
printf("---------global_num = %d--------\n", global_num);
pthread_mutex_init(&mlock, NULL); //初始化锁
pthread_create(&A_tid, NULL, af, (void*)afstr); //创建线程A
pthread_join(A_tid, NULL); //等待线程A执行完毕
printf("---------global_num = %d--------\n", global_num);
pthread_create(&B_tid, NULL, ae, (void*)aestr); //创建线程B
pthread_join(B_tid, NULL); //等待线程A执行完毕
printf("---------global_num = %d--------\n", global_num);
pthread_mutex_destroy(&mlock); //销毁锁
}
int main () {
operation();
printf("##########已下是锁操作############\n");
add_suo();
printf("----main-----global_num = %d--------\n", global_num);
return 0;
}
执行结果如下:
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 139772155049728 ===== 139772155049728
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 139772155049728
回到主线程==========完毕=======
##########已下是锁操作############
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
---------global_num = 11--------
it is ae_func operation.....
---------global_num = 20--------
ae operation funished!
---------global_num = 20--------
----main-----global_num = 20--------
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140437397698304 ===== 140437397698304
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140437397698304
回到主线程==========完毕=======
##########已下是锁操作############
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
---------global_num = 11--------
it is ae_func operation.....
---------global_num = 20--------
ae operation funished!
---------global_num = 20--------
----main-----global_num = 20--------
zhangleilei@compile_8:~/bin/test_C/pointer$ ./edit
当前线程id: 140268415817472 ===== 140268415817472
正在执行此线程=====================================
=======Hellopthread====
thread Tid has finished!!=======
Tid = 140268415817472
回到主线程==========完毕=======
##########已下是锁操作############
---------global_num = 10--------
it is af_func operation.....
---------global_num = 11--------
af operation funished!
---------global_num = 11--------
it is ae_func operation.....
---------global_num = 20--------
ae operation funished!
---------global_num = 20--------
----main-----global_num = 20--------
可以看出,正确使用锁后,每次全局变量global_num的值都是固定的,根本不会有脏数据出现,这便是加锁的含义,可以仔细体会。
J5. 注意事项
1.若用到pthread.h头文件,在编译的时候记得加上-lpthread,不然出错,如下:
zhangleilei@compile_8:~/bin/test_C/pointer$ gcc func_000.c
/tmp/ccRYvCYs.o: In function `operation':
func_000.c:(.text+0x525): undefined reference to `pthread_create'
func_000.c:(.text+0x536): undefined reference to `pthread_join'
/tmp/ccRYvCYs.o: In function `add_suo':
func_000.c:(.text+0x6a9): undefined reference to `pthread_create'
func_000.c:(.text+0x6ba): undefined reference to `pthread_join'
func_000.c:(.text+0x6f5): undefined reference to `pthread_create'
func_000.c:(.text+0x706): undefined reference to `pthread_join'
collect2: error: ld returned 1 exit status
zhangleilei@compile_8:~/bin/test_C/pointer$ gcc func_000.c -lpthread
zhangleilei@compile_8:~/bin/test_C/pointer$
2.如果用的是Makefile编译,后面加上即可,如下:
zhangleilei@compile_8:~/bin/test_C/pointer$ cat Makefile
edit : func_000.o
gcc -o edit func_000.o -lpthread
func_000.o : func_000.c
gcc -c func_000.c -lpthread
clean:
rm edit *.o
3.关于死锁:程序中有一个执行流没有释放资源,导致其它想获取该互斥锁的执行流进入阻塞等待,而该执行流还想申请对方的锁,形成闭环,导致死锁
简单直白点就是:假如有两个全局变量a,b;线程c加锁后访问a,线程e加锁后访问b,他两都没释放资源锁,就在此时,线程c又申请访问b,线程e又申请访问a,所以双方都在等待,形成闭环死结!
死锁的4个条件
1、互斥:每一把锁都只能被一个执行力占用
2、请求+保持:每一个执行流占用一个锁,但仍在申请新的锁
3、循环等待:若干个执行力在拥有和占用情况下,形成了一个闭环
4、不可剥夺:互斥锁只能被当前拥有者释放
避免死锁
1、破坏必要条件
2、加锁顺序一致
3、避免锁的不被释放
4、一次性分配资源
4.注:
互斥锁要定义为全局变量,且在创建线程之前进行初始化,在线程退出之后进行销毁,
在访问临界资源之前记得进行加锁操作,
在所有可能退出的地方都需要解锁(如果一个执行流进行加锁之后,该执行流直接退出掉,会导致其他想要获取该互斥锁的执行流卡死:死锁的现象之一)
如果加锁了但是没有进行解锁,会造成所有想要加该互斥锁的执行流陷入阻塞等待(死锁)
J6. 相关函数
1.pthread_create
格式:int pthread_create(pthread_t * thread,
const pthread_attr_t * attr,
void * (*start_routine)(void *),
void * arg);
作用:用于创建新线程
参数:
thread:返回创建的线程的线程ID,是一个指向无符号整数值的指针。
attr: 默认值为NULL,目前没有用,不需要修改。是一个指向用于定义线程属性(如分离状态、调度策略等)的结构的指针。
start_routine: 是一个指向线程执行子程序的指针。子程序的返回类型和参数类型必须是 void * 类型。如果同时传递多个指针需要用到结构体。
arg: void类型的指针,其中包含前面参数中定义的函数的参数
2.pthread_exit
格式:void pthread_exit(void * retval);
作用:用于终止线程
参数:
retval: 它是一个指向整型变量的指针,该整数储存线程终止的返回状态。读取的整型变量必须为全局变量,以便让任何等待加入该线程的线程可以读取其返回状态
3.pthread_join
格式:int pthread_join(pthread_t th,
void **thread_return);
作用:用于等待线程终止
将子线程并入主线程,主线程会一直阻塞直至子线程执行结束(收到目标子线程的返回值)后,才回收子线程资源,解除阻塞状态,并继续执行主线程
int pthread_join(pthread_t th,
void **thread_return);
参数:
th:当前线程等待加入的目标线程的线程id。
thread_return:指向th中提到的线程的退出状态存储位置的指针
4.phread_self
格式:pthread_t phread_self(void);
作用:用于获取当前线程id
5.pthread_equal
格式:int pthread_equal(pthread_t t1, pthread_t t2);
作用:用于比较两个线程是否相同。如果两个线程相等则返回一个非零值,否则返回0
参数:
t1:第一个线程id
t2:第二个线程id
6.pthead_cancel
格式:int pthead_cancel(pthread_t thread);
作用:用于向线程发送取消请求
参数:
thread:用于指定发送Cancel信号的目标线程
与线程取消执行相关的一共有两个属性,分别是:
取消执行的状态,线程的取消执行的状态一共有两个:
PTHREAD_CANCEL_ENABLE:这个状态表示这个线程是可以取消的,也是线程创建时候的默认状态。
PTHREAD_CANCEL_DISABLE:这个状态表示线程是不能够取消的,如果有一个线程发送了一个取消请求,那么这个发送取消消息的线程将会被阻塞直到线程的取消状态变成 PTHREAD_CANCEL_ENABLE 。
取消执行的类型,取消线程执行的类型也有两种:
PTHREAD_CANCEL_DEFERRED:当一个线程的取消状态是这个的时候,线程的取消就会被延迟执行,知道线程调用一个是取消点的(cancellation point)函数,比如 sleep 和 pthread_testcancel ,所有的线程的默认取消执行的类型就是这个类型。
PTHREAD_CANCEL_ASYNCHRONOUS:如果线程使用的是这个取消类型那么线程可以在任何时候被取消执行,当他接收到了一个取消信号的时候,马上就会被取消执行,事实上这个信号的实现是使用 tgkill 这个系统调用实现的。
事实上我们很少回去使用 PTHREAD_CANCEL_ASYNCHRONOUS ,因为这样杀死一个线程会导致线程还有很多资源没有释放,会给系统带来很大的灾难,比如线程使用 malloc 申请的内存空间没有释放,申请的锁和信号量没有释放,尤其是锁和信号量没有释放,很容易造成死锁的现象。
7.pthread_testcancel
格式:pthread_testcancel();
作用:用于检测线程是否被取消了,是一个取消点,这个函数是一个取消点,当执行这个函数的时候,程序就会取消执行
8.pthread_detach
格式:int pthread_detach (pthread_t thread);
作用:用于分离线程
pthread_detach这个函数就是用来分离主线程和子线程,这样做的好处就是当子线程退出时系统会自动释放线程资源。
主线程与子线程分离,子线程结束后,资源自动回收
9.pthread_attr_init
格式:pthread_attr_t attr;
pthread_attr_init(&attr); // 对变量 attr 进行初始化操作
作用:初始化线程的基本属性
使用 pthread_attr_init 对属性变量进行初始化操作。
使用各种各样的函数对属性 attr 进行操作,比如 pthread_attr_setstacksize,这个函数的作用就是用于设置线程的栈空间的大小。
使用 pthread_attr_destroy 释放线程属性相关的系统资源
10.pthread_attr_destroy
格式:pthread_attr_destroy(&attr);
作用:释放线程属性的相关资源
11.pthread_attr_setstacksize
格式:pthread_attr_setstacksize(&attr, 24 MiB); // 设置栈帧大小为 24 MiB 这里使用了一个小的 trick 大家可以看一下 MiB 的宏定义
作用:用于设置线程执行栈的大小
12.pthread_attr_setstack
格式:pthread_attr_setstack(&attr, stack, 2 MiB); // 使用属性设置函数设置栈的位置 栈的最低地址为 stack 栈的大小等于 2 MiB
作用:设置线程执行栈的栈顶和栈的大小
13.pthread_mutex_init
格式:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
作用:初始化一个互斥锁
参数:
mutex:指定的互斥锁
mutexattr:互斥锁的属性,为NULL表示默认属性
返回值: 成功:0
【互斥锁的操作】
第一步:初始化互斥锁
mutex用 pthread_mutex_t 数据类型表示,在使用互斥锁前,必须先对它进行初始化
初始化互斥锁有两个种方法:
一种静态分配互斥锁
一种动态分配互斥锁(常用)
1) 静态分配的互斥锁:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2)动态分配互斥锁(常用):
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
14.pthread_mutex_lock
格式:int pthread_mutex_lock(pthread_mutex_t *mutex);
作用:对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁
参数:
mutex:指定的互斥锁
返回值:
成功:0
失败:非0
15.pthread_mutex_trylock
格式:int pthread_mutex_trylock(pthread_mutex_t *mutex);
作用:对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0
16.pthread_mutex_unlock
格式: int pthread_mutex_unlock(pthread_mutex_t * mutex);
作用:对指定的互斥锁解锁
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0
17.函数名pthread_mutex_destroy
格式: int pthread_mutex_destroy(pthread_mutex_t *mutex);
作用:销毁指定的一个互斥锁
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0。
/*****************************************************
*线程操作:单线程、多线程操作
* **************************************************/
char *str = "Hellopthread";
void* funcp(void* arg)
{
long tid;
tid = pthread_self();//获取当前线程的id
printf("当前线程id: %ld ===== %ld\n", tid, pthread_self());
printf("正在执行此线程=====================================\n");
printf("=======%s====\n", (char*)arg);
return NULL;
}
void operation()
{
pthread_t Tid; //1.定义一个线程
pthread_create(&Tid, NULL, funcp, (void*)str); //2.创建线程并执行函数funcp
pthread_join(Tid, NULL); //3.主线程等待Tid线程执行完后,然后主线程继续往下执行
printf("thread Tid has finished!!=======\n");
printf("Tid = %ld\n", Tid);
printf("回到主线程==========完毕=======\n");
}
int main () {
operation();
return 0;
}
######################################################################
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* task(void* arg) {
while(1) {
pthread_testcancel(); // 测试是否被取消执行了
}
return NULL;
}
int main() {
void* res;
pthread_t t;
pthread_create(&t, NULL, task, NULL);
int s = pthread_cancel(t); // 取消函数的执行
if(s != 0)
fprintf(stderr, "cancel failed\n");
pthread_join(t, &res);
assert(res == PTHREAD_CANCELED);
return 0;
}
#############################################################################
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define MiB * 1 << 20
int times = 0;
void* stack_overflow(void* args) {
printf("times = %d\n", ++times);
char s[1 << 20]; // 1 MiB
stack_overflow(NULL);
return NULL;
}
int main() {
pthread_attr_t attr;
pthread_attr_init(&attr); // 对变量 attr 进行初始化操作
pthread_attr_setstacksize(&attr, 24 MiB); // 设置栈帧大小为 24 MiB 这里使用了一个小的 trick 大家可以看一下 MiB 的宏定义
pthread_t t;
pthread_create(&t, &attr, stack_overflow, NULL);
pthread_join(t, NULL);
pthread_attr_destroy(&attr); // 释放线程属性的相关资源
return 0;
}
#############################################################################
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define MiB * 1 << 20
int times = 0;
static
void* stack_overflow(void* args) {
printf("times = %d\n", ++times);
char s[1 << 20]; // 1 MiB
stack_overflow(NULL);
return NULL;
}
int main() {
pthread_attr_t attr;
pthread_attr_init(&attr);
void* stack = malloc(2 MiB); // 使用 malloc 函数申请内存空间 申请的空间大小为 2 MiB
pthread_t t;
pthread_attr_setstack(&attr, stack, 2 MiB); // 使用属性设置函数设置栈的位置 栈的最低地址为 stack 栈的大小等于 2 MiB
pthread_create(&t, &attr, stack_overflow, NULL);
pthread_join(t, NULL);
pthread_attr_destroy(&attr); // 释放系统资源
free(stack); // 释放堆空间
return 0;
}