「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」
在Linux环境中使用POSIX Thread
API可以很方便的写出多线程程序,本系列将对其API进行介绍, 本文为系列的第一篇文章, 主要介绍线程的基础操作。
在Linux中同一进程中的不同线程:
共享:
- 打开的文件
- 当前的工作目录
- 用户和组
- 信号和信号句柄
拥有自己的:
- 线程Id
errno
错误码 (使用thread-local实现)- 优先级
- 独立的线程栈
API
以下API均定义在头文件pthread.h
中, 使用GCC编译时需要链接到pthread
库上, 如下所示
gcc ./test.c -l pthread
pthread_create
pthread_create
用于创建线程, 如果线程创建成功该函数返回0, 创建失败返回错误码,创建失败的原因可能是:
EAGAIN
系统资源不足或者进程中的线程数量达到限制, 我们可以通过cat /proc/sys/kernel/threads-max
查看当前系统对于线程数量的限制EINVAL
attr
参数中有非法的设置EPERM
attr
参数中有不被允许的设置
该函数定义了以下参数:
thread
指向线程id的指针, 创建线程成功后会通过该指针将线程id保存到其中attr
用于设置线程相关的参数, 如果想使用默认参数传NULL即可。如: 设置线程的栈大小以及栈的地址(此场景常见于嵌入式设备需要限制线程栈空间大小的场景)start_routine
线程要执行的函数, 该函数的出入参均为一个无类型指针void*
arg
传给函数的参数
#include <pthread.h>
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);
pthread_self
pthread_self
用于获取当前线程的id.
pthread_t pthread_self(void);
pthread_join
调用pthread_join
会等待目标线程执行完成之后才退出, 并获取线程的执行结果, 下文中我们将会给出Demo, 返回是否执行成功.
int pthread_join(pthread_t thread, void **retval);
pthread_exit
终止当前线程的运行(调用此方法的线程), 并设置线程的返回值, 如果有其他线程正在阻塞等待(调用pthread_join
)此线程返回它将接收到这一返回值。
void pthread_exit(void *retval);
pthread_detached
pthread_detached
用于分离一个线程, 调用此方法的线程在终止后资源会被回收掉,并且不可等待(即其他线程不可调用pthread_join
等待此线程)
int pthread_detach(pthread_t thread);
Demo
创建线程并传递参数
以下Demo展示了如何创建一个线程, 并传入参数以及取得该线程的返回值并进行打印。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void* just_print(void *arg) {
char *str = (char *)arg;
printf("Thread say: %s\n", str);
char *res_str = (char *)malloc(256);
sprintf(res_str, "My thread id is %ld", pthread_self());
return (void *)res_str;
}
int main() {
pthread_t tid;
void* res;
printf("thread create\n");
int s = pthread_create(&tid, NULL, just_print, "Hello");
if (s) {
perror("thread create failed");
return -1;
}
pthread_join(tid, &res);
printf("thread exit \n");
printf("Main thread recv msg: %s\n", (char *)res);
return 0;
}
输出如下所示:
限制线程栈空间大小
上文中提到在创建线程时,我们可以通过pthread_attr_t
结构体设置一些参数来改变线程的一些行为。 在一些特殊场景下这个功能是非常实用的, 比如:
- 如果程序运行在嵌入式设备(内存受限), 为了节约内存我们可以通过
pthread_attr_t
来调小线程栈的大小以节约内存 - 在执行一些比较复杂运算的情况下,我们也可通过此参数来调大线程的栈内存以获取更多的栈空间
操作系统的默认线程栈大小我们可以通过ulimit -s
进行查看, 效果如下
如一下Demo所示, 我们通过pthread_attr_init
来初始化pthread_attr_t
结构体, 之后调用pthread_attr_set_stacksize
来设置线程栈的大小。
注意设置线程栈大小的时候, 如果设置的栈大小小于PTHREAD_STACK_MIN
时会返回非法参数的错误, 对于部分系统如果栈大小不是系统的页大小的倍数,也会引发错误。
如以下Demo所示, 我们使用THREAD_STACK_SIZE
来表示分配给线程的栈空间大小, ALLOC_SIZE
来表示线程的要执行的方法会申请的栈空间大小.
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#define THREAD_STACK_SIZE 4096 * 6
#define ALLOC_SIZE 4096 * 4
void* malloc_mem(void *arg) {
char msg[ALLOC_SIZE];
printf("stack malloc done! \n");
}
int main() {
pthread_t tid;
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) {
perror("init attr failed");
return -1;
}
int r = pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);
if(r != 0) {
printf("set stack size failed, err=%s\n", strerror(r));
return -1;
}
int size = 1024;
int s = pthread_create(&tid, &attr, malloc_mem, &size);
if (s) {
perror("thread create failed:");
return -1;
}
pthread_join(tid, NULL);
return 0;
}
测试用例1, THREAD_STACK_SIZE = ALLOC_SIZE = 4096 * 6
, 输出如下所示:
系统提示Segmentation fault
, 原因是栈空间不足.
测试用例2, THREAD_STACK_SIZE = 4096 * 6, ALLOC_SIZE = 4096 * 4
, 输出如下所示:
测试用例3 THREAD_STACK_SIZE = 4096 * 1, ALLOC_SIZE = 1024
输出如下所示
提示参数错误, 原因时分配给线程的栈空间小于PTHREAD_STACK_MIN