个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创Linux系统基础-多线程超详细讲解(2)_线程控制
收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. POSIX线程库
2. 创建线程
3. 线程ID及进程地址空间布局
4. 线程终止
return 函数
pthread_exit 函数
pthread_cancel 函数
5. 线程等待
为什么要等待线程?
函数 pthread_join
6. 分离线程
1. POSIX线程库
1. 与线程有关的函数构成了一个完整的系列, 绝大多数函数的名字都是以 "pthread_" 打头的
2. 要使用这些函数库, 要通过引入头文件 <pthread.h>
3. 链接这些线程函数库时要使用编译器命令的 "-lpthread" 选项
比如我们写makefile时, 需要添加 -lptheard 选项
test_thread:testThread.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f test_thread
在我们编写程序时, 我们同样需要加上线程库的头文件, 后面会有解释;
2. 创建线程
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查 :
1. 传统的一些函数, 成功返回0, 失败返回-1, 并且对全局变量errno赋值以指示错误.
2. pthreads 函数出错时不会设置全局变量errno (而大部分其他 POSIX 函数会这样做). 而是将错误代码通过返回值返回
3. pthreads 同样也提供了线程内的 errno 变量, 以支持其他使用 errno 的代码, 对于 pthreads 函数的错误, 建议通过返回值判定, 因为读取返回值要比读取线程内的 errno 变量的开销更小
比如我们下面的代码 :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void* rout(void *arg)
{
int i;
for(; ; )
{
printf("I am thread 1\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
int ret;
if((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
{
fprintf(stderr, "pthread_create : %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
int i;
for(; ; )
{
printf("I am main thread\n");
sleep(1);
}
}
补充 : pthread_t
pthread_t : 用于唯一标识一个线程, 当你创建一个新线程时, 系统会生成一个 pthread_t 类型的值, 用于该线程.
代码分析 :
主函数 :
定义了一个线程变量 pthread_t tid 来存储新创建的线程的 ID
调用了 pthread_create 创建一个新线程, 并执行 rout 函数
检查 pthread_create 的返回值已确认线程是否创建成功, 如果失败, 打印错误信息并退出
线程函数 rout :
函数定义为 void* rout (void* arg), 接收一个 void* 类型的参数或者为空
使用无限循环打印日志
输出结果:
3. 线程ID及进程地址空间布局
1. pthread_create函数会产生一个线程 ID, 存放在第一个参数指向的地址中, 该线程 ID 和前面说的线程 ID 不是一回事.
2. 前面所说的线程 ID 属于进程调度的范畴, 因为线程是轻量级进程, 是操作系统调度器的最小单位, 所以需要一个数值来唯一表示该线程.
3. pthread_create函数第一个参数指向一个虚拟内存单元, 该内存单元的地址即为新创建线程的 ID, 属于NPTL 线程库的范畴, 线程库的后续操作, 就是根据该线程 ID 来操作线程的
4. 线程库 NPTL 提供了pthread_self函数, 可以获得自身的 ID:
示例代码 :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
void *gettid(void *arg)
{
//tid1,2,3,4 线程
// 将 arg 转换为字符串并打印
const char *thread_name = (const char *)arg;
while (true)
{
printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");
// 主线程 ID
while (true)
{
printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
sleep(1);
}
return 0;
}
结果展示 :
pthread_t 到底是什么类型呢? 取决于实现, 对于Linux目前实现的 NPTL 而言, pthread_t 类型的线程 ID , 本质就是一个进程地址空间上的一个地址
4. 线程终止
如果需要只终止某个线程而不终止整个进程, 可以有三种方法 :
1. 从线程函数 return, 这种方法对主线程不适用, 从 main 函数 return 相当于调用 exit
2. 线程可以调用 pthread_exit 终止自己
3. 一个线程可以调用 pthread_cancel 终止同一进程的另一个线程
return 函数
实例代码 :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
void *gettid(void *arg)
{
//tid1,2,3,4 线程
// 将 arg 转换为字符串并打印
const char *thread_name = (const char *)arg;
while (true)
{
printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
if(thread_name == "Thread 1") return 0;
else if(thread_name == "Thread 2") return 0;
else if(thread_name == "Thread 3") return 0;
else return 0;
sleep(1);
}
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");
// 主线程 ID
while (true)
{
printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
sleep(1);
}
return 0;
}
pthread_exit 函数
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意, pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局变量的或者是用 malloc 分配的, 不能在线程函数栈上分配, 因为当其他线程得到这个返回指针时线程函数已经退出了~~
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
void *gettid(void *arg)
{
//tid1,2,3,4 线程
// 将 arg 转换为字符串并打印
const char *thread_name = (const char *)arg;
while (true)
{
printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
if(thread_name == "Thread 1") pthread_exit(NULL);
else if(thread_name == "Thread 2") pthread_exit(NULL);
else if(thread_name == "Thread 3") pthread_exit(NULL);
else pthread_exit(NULL);
sleep(1);
}
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");
// 主线程 ID
while (true)
{
printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
sleep(1);
}
return 0;
}
pthread_cancel 函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
void *gettid(void *arg)
{
//tid1,2,3,4 线程
// 将 arg 转换为字符串并打印
const char *thread_name = (const char *)arg;
while (true)
{
printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
// if(thread_name == "Thread 1") pthread_exit(NULL);
// else if(thread_name == "Thread 2") pthread_exit(NULL);
// else if(thread_name == "Thread 3") pthread_exit(NULL);
// else pthread_exit(NULL);
sleep(1);
}
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");
sleep(5);
pthread_cancel(tid1);
pthread_cancel(tid2);
pthread_cancel(tid3);
pthread_cancel(tid4);
// 主线程 ID
while (true)
{
printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
sleep(1);
}
return 0;
}
5. 线程等待
为什么要等待线程?
1. 已经退出的线程, 其空间没有被释放, 仍然在进程的地址空间内.
2. 创建新的线程不会服用刚才退出的线程的地址空间
函数 pthread_join
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
调用该函数的线程将挂起等待, 直到 id 为 thread 的线程终止, thread 线程以不同的方法终止, 通过 pthread_join 得到的终止状态是不同的, 总结如下 :
1. 如果 thread 线程通过 return 返回, value_ptr 所指向的单元里存放的是 thread 线程函数的返回值
2. 如果 thread 线程被别的线程调用 pthread_cancel 异常中断, value_ptr 所指向的单元里面存放的是常数 PTHREAD_CANCELED
3. 如果 thread 线程是自己调用 pthread_exit 终止的, value_ptr 所指向的单元放的是传给 pthread_exit 的参数
4. 如果对 thread 线程的终止状态不感兴趣, 可以传 NULL 给 value_ptr 参数
实例代码 :
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
void *thread1(void *arg)
{
printf("thread 1 returning ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void *)p;
}
void *thread2(void *arg)
{
printf("thread 2 returning ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void *)p);
}
void *thread3(void *arg)
{
while(1)
{
printf("thread 3 is running ...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
void *ret;
//thread 1 return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread return , thread id %X, return code:%d\n", tid, *(int*)ret);
free(ret);
//thread 2 exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread return , thread id %X , return code : %d\n", tid, *(int*)ret);
free(ret);
// thread 3 cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if(ret == PTHREAD_CANCELED)
printf("thread return , thread id %X, return code : PTHREAD_CANCELED\n", tid);
else
printf("thread return , thread id %X , return code : NULL\n", tid);
}
线程函数定义:
thread1 :
动态分配一个整数, 返回指向该整数的指针
thread2:
动态分配一个整数, 使用 pthread_exit 返回指针
thread3:
无限循环打印输出消息, 表示线程正在运行
主函数逻辑:
创建等待 thread1:
使用 thread_create创建线程 thread1, 使用 thread_join 等待该线程结束, 并获取返回值, 输出线程 ID 和返回的整数数值, 最后释放动态分配的内存
创建并等待 thread2
使用 pthread_create 创建线程 thread2, 同样使用 pthread_join 等待线程结束并获取返回值, 输出线程 ID 和返回值, 释放动态内存
创建 thread3 并取消:
创建 thread3 , 主线程休眠3秒, 然后调用 pthread_cancel 来取消 thread3, 使用 pthread_join等待 thread3 结束, 并获取返回值, 检查返回值, 如果返回值是 PTHREAD_CANCELED, 则表示线程被取消, 输出相关信息~
6. 分离线程
1. 默认情况下, 新创建的线程是 joinable 的, 线程退出后, 需要对其进行 pthread_join 操作, 否则无法释放资源, 从而造成系统泄露.
2. 如果不关心线程的返回值, join 是一种负担, 这个时候, 我们可以告诉系统, 当线程退出时, 自动释放线程资源.
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离, 也可以是线程自己分离
pthread_detach(pthread_self());
joinable 和分离是冲突的, 一个线程不能既是 joinable 又是分离
实例代码 :
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
void *thread_run(void *arg)
{
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
}
int main()
{
pthread_t tid;
if(pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0)
{
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); //很重要, 要让线程先分离, 在等待
if(pthread_join(tid, NULL) == 0)
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}