文章目录
- 前言
- 一、线程相关操作函数
- 1. pthread_create
- 2. pthread_join
- 3. pthread_exit
- 4. pthread_cancel
- 5. pthread_detach
- 6. 示例代码
前言
在 Linux 中并不存在真正意义上的线程, 而是通过复用进程的结构来实现的, 叫做轻量级进程. 线程是一个进程内部的一个执行流, 而一个进程最少都有一个线程, 就是该进程本身, 线程在进程内部运行,本质是在进程地址空间内运行, 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流.
进程是资源分配的基本单位, 线程是调度的基本单位.
一、线程相关操作函数
1. pthread_create
头文件: #include <pthread.h>
函数声明: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- 返回值: 成功返回 0, 失败返回 errno.
- thread: 输出型参数, 返回线程 id.
- attr: 设置线程的属性, attr 为 NULL 表示使用默认属性.
- start_routine: 函数指针, 该线程执行的函数.
- arg: 传递给 start_routine 的参数.
功能: 创建一个线程, 该线程会执行 start_routine 函数.
示例代码(入口函数不传参):
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
for(int i = 0; i < 5; ++i)
{
cout << "Hello World" << endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
pthread_join(tid, nullptr);
return 0;
}
运行结果:
在编译时会出现如下问题:
提示我们使用的函数是未定义的, 原因就在于使用了线程库提供的函数没有链接该库, 在 makefile 文件中指明库名即可, 如下:
此时再编译就没问题了, 运行结果如下:
程序运行后, 该线程执行流就会去执行其入口函数了, 通过监控脚本观察此时确实有两个线程:
可以看到监控显示的信息中, 有一个熟悉的字段 PID, 这个 PID 也就是我们跑起来的一个进程, 而字段 LWP(Light Weight Process) 表示轻量级进程, 可以得知在该进程中存在两个执行流, 也就是两个线程, 而其中一个线程的 LWP 和 PID 是一致的, 该线程就是我们的主线程, 此前我们认为的一个进程在 Linux 中其实就是一个只有一个执行流的进程, 所以说在 Linux 中并不存在真正意义上的线程, 在一个进程的内部可以存在多个线程, 并且每个线程都共享所处进程的部分数据, 大致如下图所示:
整个图表示一个进程, 里面的各个 task_struct 表示一个线程.
示例代码(入口函数传递内置类型):
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
const char* buf = static_cast<const char*>(arg);
for(int i = 0; i < 5; ++i)
{
cout << buf << "Hello World" << endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
char buf[] = "[wcm]:";
pthread_create(&tid, nullptr, ThreadRoutine, buf);
pthread_join(tid, nullptr);
return 0;
}
运行结果:
入口函数的参数类型为 void*, 表示我们可以传递任意类型的参数, 在入口函数中使用时, 只需要进行类型转换即可, 返回值也是一样的.
2. pthread_join
头文件: #include <pthread.h>
函数声明: int pthread_join(pthread_t thread, void **retval);
- 返回值: 成功返回 0, 失败返回 errno.
- thread: 等待的线程 id.
- retval: 被等待线程的返回值.
功能: 主线程阻塞式的等待回收一个线程.
实例代码:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
for(int i = 0; i < 5; ++i)
{
cout << "Hello World" << endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
pthread_join(tid, nullptr);
return 0;
}
如果 pthread_join 的第二个参数设置为空, 表示不接收等待线程的返回值, 要想接收返回值, 代码如下:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
const char* buf = static_cast<const char*>(arg);
for(int i = 0; i < 5; ++i)
{
cout << buf << "Hello World" << endl;
sleep(1);
}
return (void*)111;
}
int main()
{
pthread_t tid;
char buf[] = "[wcm]:";
pthread_create(&tid, nullptr, ThreadRoutine, buf);
void* ret;
pthread_join(tid, &ret);
int res = reinterpret_cast<uint64_t>(ret);
sleep(1);
cout << "exit code: " << res << endl;
return 0;
}
运行结果:
3. pthread_exit
头文件: #include <pthread.h>
函数声明: void pthread_exit(void *retval);
- retval: 线程的返回值.
功能: 如果在一个线程内使用 exit 来终止该线程, 那么该进程所属的整个进程也会被终止, pthread_exit 只会终止当前线程, 并不会影响进程乃至其他任何线程执行流.
示例代码:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
const char* buf = static_cast<const char*>(arg);
for(int i = 0; i < 5; ++i)
{
if(i == 3)
{
exit(1);
}
cout << buf << "Hello World" << endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
char buf[] = "[wcm]:";
pthread_create(&tid, nullptr, ThreadRoutine, buf);
pthread_join(tid, nullptr);
sleep(10);
cout << "main() quit" << endl;
return 0;
}
先看看线程通过 exit 终止会怎样:
可以看到, 通过 exit 终止的线程不止本身被终止了, 连带着整个进程都被终止了, 原因也很简单, exit 本身就是终止一个进程的, 而多个线程同属一个进程中, 如果该线程的入口函数中通过 exit 终止了, 那么理所应当的整个进程都会被终止, 而 pthread_exit 只会终止调用线程, 并且还可以携带返回值:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
const char* buf = static_cast<const char*>(arg);
for(int i = 0; i < 5; ++i)
{
if(i == 3)
{
char* buf = new char[64];
snprintf(buf, 64, "i am return val");
pthread_exit(buf);
}
cout << buf << "Hello World" << endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
char buf[] = "[wcm]:";
pthread_create(&tid, nullptr, ThreadRoutine, buf);
void* ret;
pthread_join(tid, &ret);
const char* res = static_cast<const char*>(ret);
cout << res << endl;
return 0;
}
运行结果:
成功收到线程终止后的返回值, 并且该线程的终止不会影响其他线程.
4. pthread_cancel
头文件: #include <pthread.h>
函数声明: int pthread_cancel(pthread_t thread);
- 成功返回 0, 失败返回 errno.
- thread: 被取消的线程 id.
功能: 取消一个线程的执行.
示例代码:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
for(int i = 0; i < 10; ++i)
{
printf("[%d]Hello World\n", i + 1);
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
for(int i = 0; i < 5; ++i)
{
sleep(1);
}
pthread_cancel(tid);
pthread_join(tid, nullptr);
cout << "main thread done" << endl;
return 0;
}
运行结果:
在主线程取消该线程后, 该线程直接结束了.
5. pthread_detach
头文件: #include <pthread.h>
函数声明: int pthread_detach(pthread_t thread);
- 成功返回 0, 失败返回 errno.
- thread: 被分离的线程 id.
功能: 分离一个线程, 该线程不需要被等待(join)了.
示例代码:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* ThreadRoutine(void* arg)
{
for(int i = 0; i < 10; ++i)
{
printf("[%d]Hello World\n", i + 1);
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
pthread_detach(tid);
cout << "main thread done" << endl;
return 0;
}
6. 示例代码
场景描述: 创建 10 个线程, 每个线程分别执行各自的累加任务, 执行完任务后结束, 最后主线程回收各线程, 程序结束.
代码:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//枚举退出状态
enum exit_code
{
OK,
ERROR
};
class thread_info
{
public:
thread_info(string name, int top, int res = 0, int status = OK)
:_name(name), _top(top)
{}
public:
//输入型参数
string _name; //线程名
int _top; //该线程要累加到的值
//输出型参数
int _res; //累加结果
int _status; //退出码
};
//线程入口函数
void* Routine(void* arg)
{
thread_info* ti = static_cast<thread_info*>(arg);
cout << "线程" << pthread_self() << "计算中..." << endl;
sleep(1);
for(int i = 1; i <= ti->_top; ++i)
{
ti->_res += i;
}
cout << "线程" << pthread_self() << "计算完毕" << endl;
sleep(1);
pthread_exit(ti);
}
int main()
{
pthread_t threads[10]; //各线程id
for(int i = 0; i < 10; ++i)
{
char buf[64];
snprintf(buf, sizeof(buf), "thread-%d", i);
thread_info* ti = new thread_info(buf, 100 + i * 5);
pthread_create(threads + i, NULL, Routine, ti);
}
void* arg;
//回收线程
for(int i = 0; i < 10; ++i)
{
pthread_join(threads[i], &arg);
thread_info* res = static_cast<thread_info*>(arg);
printf("线程%d已回收,其累加范围是[1,%d],累加结果:%d,退出码:%d\n", threads[i], res->_top, res->_res, res->_status);
delete res;
}
cout << "线程回收完毕" << endl;
return 0;
}
运行结果:
程序运行起来同时运行的监控脚本:
从结果可以看出, 确实创建了 10 个线程来执行累加结果, 而最后也成功回收了各线程, 在创建线程时, 给入口函数传参不仅可以传递基础类型, 也可以传递复杂的结构类型, 代码中也是传递了一个自定义的类当作参数, 而从执行顺序可以得知, 线程的执行顺序是不确定的, 在监控脚本中可以看到运行的线程, 其中 PID 是整个进程的 PID, 因为一个进程内可以存在很多个执行流, 所以这些线程执行流的 PID 都是一样的, 而 LWP 则是每个线程都不同的.