一 Pthread 并发编程——深入剖析线程基本元素和状态( 三 )


  • 使用 malloc 函数申请内存空间 , 这部分空间主要在堆当中 。
  • 使用 mmap 系统调用在共享库的映射区申请内存空间 。
使用 malloc 函数申请内存空间#include <stdio.h>#include <pthread.h>#include <stdlib.h>#define MiB * 1 << 20int times = 0;staticvoid* stack_overflow(void* args) {printf("times = %d\n", ++times);char s[1 << 20]; // 1 MiBstack_overflow(NULL);return NULL;}int main() {pthread_attr_t attr;pthread_attr_init(&attr);void* stack = malloc(2 MiB); // 使用 malloc 函数申请内存空间 申请的空间大小为 2 MiBpthread_t t;pthread_attr_setstack(&attr, stack, 2 MiB); // 使用属性设置函数设置栈的位置 栈的最低地址为 stack 栈的大小等于 2 MiBpthread_create(&t, &attr, stack_overflow, NULL);pthread_join(t, NULL);pthread_attr_destroy(&attr); // 释放系统资源free(stack); // 释放堆空间return 0;}上述程序的执行结果如下图所示:
一 Pthread 并发编程——深入剖析线程基本元素和状态

文章插图
从上面的执行结果可以看出来我们设置的栈空间的大小为 2MB 成功了 。在上面的程序当中我们主要使用 pthread_attr_setstack 函数设置栈的低地址和栈空间的大小 。我们申请的内存空间内存布局大致如下图所示:
一 Pthread 并发编程——深入剖析线程基本元素和状态

文章插图
使用 mmap 申请内存作为栈空间#define _GNU_SOURCE#include <stdio.h>#include <pthread.h>#include <stdlib.h>#include <sys/mman.h>#define MiB * 1 << 20#define STACK_SIZE 2 MiBint times = 0;staticvoid* stack_overflow(void* args) {printf("times = %d\n", ++times);char s[1 << 20]; // 1 MiBstack_overflow(NULL);return NULL;}int main() {pthread_attr_t attr;pthread_attr_init(&attr);void* stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);if (stack == MAP_FAILED)perror("mapped error:");pthread_t t;pthread_attr_setstack(&attr, stack, STACK_SIZE);pthread_create(&t, &attr, stack_overflow, NULL);pthread_join(t, NULL);pthread_attr_destroy(&attr);free(stack);return 0;}在上面的程序当中我们使用 mmap 系统调用在共享库空间申请了一段内存空间,并且将其做为栈空间,我们在这里就不将程序执行的结果放出来了,上面整个程序和前面的程序相差不大,只是在申请内存方面发生了变化,总体的方向是不变的 。
根据前面知识的学习,我们可以知道多个线程可以共享同一个进程虚拟地址空间,我们只需要给每个线程申请一个栈空间让线程执行起来就行 , 基于此我们可以知道多个线程的执行流和大致的内存布局如下图所示:
一 Pthread 并发编程——深入剖析线程基本元素和状态

文章插图
在上图当中不同的线程拥有不同的栈空间和每个线程自己的寄存器现场,正如上图所示,栈空间可以是在堆区也可以是在共享库的映射区域,只需要给线程提供栈空间即可 。
深入理解线程的状态在 pthread 当中给我们提供了一个函数 pthread_cancel 可以取消一个正在执行的线程,取消正在执行的线程之后会将线程的退出状态(返回值)设置成宏定义 PTHREAD_CANCELED。我们使用下面的例子去理解一下线程取消的过程:
#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;}在上面的程序当中我们在主线程当中使用函数 pthread_cancel 函数取消线程的执行,编译执行上面的程序是可以通过的,也就是说程序正确执行了 , 而且 assert 也通过了 。我们先不仔细去分析上面的代码的执行流和函数的意义 。我们先需要了解一个线程的基本特性 。
与线程取消执行相关的一共有两个属性,分别是: