线程概念
什么是线程
- LWP:light weight process轻量级进程,本质仍是进程(在Linux环境下)
- 进程:独立地址空间,拥有PCB
- 线程:也有PCB,但没有独立的地址空间(共享)
- 区别:在于是否共享地址空间。 独居(进程);合租(线程)
- Linux下:线程:最小执行单位,进程最小分配资源单位,可看成只有一个线程的进程
Linux内核线程实现原理
类unix系统中,早期是没有线程的概念的,80年代才引入,借助进程机制实现了线程的概念,因此在这类系统中,进程和线程关系密切。
- 轻量级进程(LWP),也有PCB,创建线程和进程的底层函数一样,都是clone函数
- 从内核看进程和线程都是一样的,都有不同的PCB,但是PCB中的三级页表是相同的。
- 进程可以蜕变成线程
- 线程可以看作寄存器和栈的集合
- 在linux下,线程是最小的执行单元,进程是最小的资源分配单元
查看LWP号:ps -Lf pid用来查看指定进程的LWP号
线程号和线程ID的区别:线程号是cpu分配时间轮片的依据,线程ID是进程区分线程的依据
补充:MMU的工作过程,首先从PCB的三级页表指针找到页目录,然后通过页目录找到页表,通过页表找到物理单元。
线程共享资源
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间(.data/.bss/.text/headp/共享库)【除掉栈区】
线程非共享资源
- 线程ID
- 处理器现场(寄存器)和栈区(内核栈)
- 独立的栈空间(用户栈)
- errno变量
- 信号屏蔽字
- 调度优先级
线程优缺点
优点:提高程序的并发性、开销小(相对进程)、共享数据方便
缺点:库函数不稳定、编写调试难不能用GDB、对信号支持不好
线程控制原语
pthread_self函数
- 介绍:获取线程ID(不是LWP号),对应进程中的getpid();
- 函数原型:
pthread_t pthread_self(void)
- 返回值:返回对应的线程ID
pthread_create函数
- 介绍:创建一个新线程,对应进程的fork函数
- 函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg
- 返回值:成功返回0,失败返回错误号
- 错误号处理:使用
char * strerror(int errnum)
来获取错误信息并输出
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
void * t_fun(void * arg) {
printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
return NULL;
}
int main(void) {
pthread_t tid;
printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
int ret = pthread_create(&tid, NULL, t_fun, NULL);
if(ret != 0) {
fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
exit(1);
}
sleep(1);
printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
return 0;
}
循环创建子线程代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
void * t_fun(void * arg) {
long long int i = (long long int )arg;
sleep(i);
printf("%lldth thread id = %lu, pid = %lu\n", i, (unsigned long)pthread_self(), (unsigned long)getpid());
return NULL;
}
int main(void) {
pthread_t tid;
for(int i = 0; i < 5; i++) {
int ret = pthread_create(&tid, NULL, t_fun, (void *)i);
if(ret != 0) {
fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
exit(1);
}
}
sleep(5);
return 0;
}
线程与共享
线程之间共享全局变量:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
int var = 100;
int main(void) {
pthread_t tid;
printf("The first var = %d\n", var);
auto f = [](void *arg) mutable -> void* {var = 200; return NULL;};
int ret = pthread_create(&tid, NULL, f, NULL);
if(ret != 0) {
fprintf(stderr, "create failed: %s", strerror(ret));
exit(1);
}
sleep(1);
printf("after the var = %d\n", var);
return 0;
}
pthread_exit函数
- 函数原型:
void pthread_exit(void *value_ptr);
- 作用:退出当前线程
- 注意:在线程中,exit、return、pthread_exit拥有完全不同的含义,其中return是使函数返回到调用函数的地方,exit为结束整个进程,pthread_exit是结束当前线程,所以在多线程编程中推荐使用pthread_exit函数
pthread_join函数
阻塞线程退出,获取线程退出状态,对应进程的wait函数
- 函数原型:
int pthread_join(pthread_t thread, void ** value_ptr);
- 作用:回收线程,并把线程的返回值出存在value_ptr中
- 返回值:成功返回0,失败返回错误代码
代码演示:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <memory.h>
using std::shared_ptr;
using std::make_shared;
struct exit_t {
exit_t(char _c, int _val, char *_str) : c(_c), val(_val) {
strcpy(str, _str);
};
char c;
int val;
char str[200];
};
void *t_fun(void *arg) {
exit_t * exit_val = (exit_t *) arg;
exit_val->c = 'z';
exit_val->val = 200;
strcpy(exit_val->str, "zhang");
pthread_exit((void *)exit_val);
}
int main(void) {
//exit_t *exit_val = (exit_t*)malloc(sizeof(exit_t));
shared_ptr<exit_t> exit_val = make_shared<exit_t>(exit_t('f', 1000, "hahaha"));
pthread_t tid = 0;
int ret = pthread_create(&tid, NULL, t_fun, (void *)&exit_val);
if(ret != 0) {
printf("create error: %s\n", strerror(ret));
exit(1);
}
sleep(1);
pthread_join(tid, (void **) & exit_val);
printf("exit->c=%c, exit->val=%d, exit->str=%s\n", exit_val->c, exit_val->val, exit_val->str);
return 0;
}
pthread_detach函数
将线程在状态层面上与主线程分离,常用于服务器编程和网络编程。
- 函数原型:
int pthread_detach(pthread_t tid);
- 作用:将子线程与主线程分离(状态上,并没有新的内存地址空间)
- 返回值:成功返回0,失败返回错误编号
代码演示
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
int main() {
auto f = [](void *arg) mutable -> void* {
int n = 3;
while(n--) {
printf("%d\n", n);
sleep(1);
}
pthread_exit((void *)1);
};
pthread_t tid = 0;
pthread_create(&tid, NULL, f, NULL);
pthread_detach(tid);
while(1) {
int ret = pthread_join(tid, NULL);
if(ret != 0) {
printf("error num: %d\n", ret);
printf("join error: %s\n", strerror(ret));
}
sleep(1);
}
return 0;;
}
pthread_cancel函数
用来杀死线程,但是有一定的延时性,需要到达固定的取消点(检查点)才能杀死线程。
终止线程方式
- 使用return结束回调函数
- 使用pthread_exit函数
- 使用pthread_cancel函数
注意事项
pthread函数具有一定的延时性,比如玩游戏必须要到某个地方(客栈等)的存档点才能存档,在Linux中可以简单的认为系统函数中都会有一个cancel检查点,如果在函数体中没有检查点,那么需要手动调用pthread_testcancel函数来设置检查点发,但是有一定的延时性。
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int main(void) {
void *(*f)(void*arg) = [](void *arg) -> void*{
int i = 0;
while(1) {
printf("xxxlife: %d\n", i++);
//sleep(1);
pthread_testcancel();
}
};
pthread_t tid;
pthread_create(&tid, NULL, f, NULL);
sleep(3);
pthread_cancel(tid);
int ret = pthread_join(tid, NULL);
if(ret != 0) {
printf("join error: %s\n", strerror(ret));
}
return 0;
}
pthread_equal函数
在目前的Linux系统中用处并不大,应为pthread_t为整型数据所以可以使用“==”来判断,但是在未来的Linux系统中可能会对pthread_t进行拓展。
- 函数原型:
int pthread_equal(pthread_t t1, pthread_t t2);
- 作用:查看两个线程是否相同
- 返回值:如果相同则返回非零,否则返回0
控制原语对比
进程 | 线程 |
---|---|
fork | pthread_create |
exit | pthread_exit |
wait | pthread_join |
kill | pthread_cancel |
getpid | pthread_self |
无分离函数 | pthread_detach |
线程属性
结构体原型:
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
我们对于线程属性暂时掌握常用的三个属性:
- 设置线程的分离状态,使用pthread_detach函数也可以做到
- 设置线程栈大小
- 设置线程警戒区的大小
什么事警戒区:警戒区为内核栈中两个栈帧之间的区域,为了防止栈溢出
线程属性初始化
pthread_init函数
- 函数原型:
int pthread_init(pthread_attr_t * attr);
- 作用:初始化一个线程属性
- 返回值:成功返回0,失败返回错误号
线程分离状态
pthread_attr_setdetachstate函数
- 函数原型:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int pthread_state)
- 参数:对于第二个参数,系统提供了两个宏分别为
THREAD_CREATE_DETACHED以及PTHREAD_CREATE_JOINABLE
- 作用:第一个宏为设置线程为游离态,第二个为加入态
- 返回值:成功返回0,失败返回错误号
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
int main(void) {
auto f = [](void * arg) mutable -> void* {
printf("thread start");
pthread_exit((void*)1);
};
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t tid;
int ret = pthread_create(&tid, &attr, f, NULL);
if(ret != 0) {
printf("create error: %s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid, NULL);
sleep(2);
if(ret != 0) {
printf("%d\n", ret);
printf("join error: %s\n", strerror(ret));
}
return 0;
}
线程的栈控制
pthread_attr_setstack函数
用来设置线程栈的地址和大小
- 函数原型:
int pthread_attr_setstack(pthread_attr_t * attr, void * stack_addr, size_t stack_size);
- 函数作用:从堆中申请空间并设置为线程栈
- 返回值:成功返回0,失败返回错误号
线程属性控制实例
代码演示
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
const int size = 0x10000000;
void * t_fun(void *arg) {
while(1) {
sleep(1);
}
pthread_exit((void *) 1);
}
int main(void) {
pthread_attr_t attr;
pthread_t tid;
pthread_attr_init(&attr);
int detached;
pthread_attr_getdetachstate(&attr, &detached);
if(detached == PTHREAD_CREATE_DETACHED) {
puts("thread detached");
}
if(detached == PTHREAD_CREATE_JOINABLE) {
puts("thread joinable");
}
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
void * stackaddr = nullptr;
int cnt = 0;
while(1) {
stackaddr = malloc(size);
if(stackaddr == nullptr) {
perror("malloc");
exit(1);
}
pthread_attr_setstack(&attr, stackaddr, size);
int ret = pthread_create(&tid, &attr, t_fun, NULL);
if(ret != 0) {
printf("creat error: %s\n", strerror(ret));
printf("%d threads has create\n", cnt);
exit(1);
}
cnt++;
}
pthread_attr_destroy(&attr);
return 0;
}