👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
一、 POSIX线程库
在上篇博客中(点击跳转),我们说过:Linux
操作系统中没有很明确的线程的概念,因为线程调度成本低 + 用进程的数据结构模拟线程(和进程使用相同的内核数据结构),因此Linux
内核将线程视为轻量级进程。所以Linux
操作系统不会直接提供线程的系统调用。只会给我们提供轻量级进程系统调用。
可是我们用户需要的是线程的接口!所以,在用户层和内核层之间,即应用层,就有了一个 pthread
线程库。它是将轻量级进程接口进行了封装。对于这个pthread
线程库(第三方库),几乎所有的Linux
平台都是默认自带这个库的。因此,pthread
线程库也称原生线程库(原来就生在Linux
系统里,只要Linux
在,这个库就在)。
总之,在Linux
中编写线程相关代码,需要使用第三方pthread
库。
注意:
- 要使用这些函数库,要通过引入头文件
<pthread.h>
。 - 链接这些线程函数库时要使用编译器命令的
-lpthread
选项。
二、快速使用线程接口
2.1 创建线程
【函数原型】
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
说明:
-
pthread_t *thread
:它是一个输出型参数,在成功创建线程后,返回线程ID
。(输出型参数:函数可以修改参数的值,并返回) -
const pthread_attr_t *attr
:用于指定线程的属性(比如优先级、状态、私有栈大小)。一般直接填nullptr
即可,表示线程将使用默认属性。 -
void *(*start_routine) (void *)
:是一个函数指针(函数地址),表示线程启动后要执行的函数。返回值和形参类型均为void*
。表示可以接收或返回任意指针类型,因为C
语言没有模板,但是想用泛型。注意:不可以定义形如void x;
这样定义变量;但可以定义void*
变量。 -
void *arg
:它是一个输入型参数,是传递给start_routine
函数的参数,它可以是任何类型的指针,用于向新线程传递数据。如果不需要给start_routine
函数传递数据,直接设置nullptr
即可。(输入型参数:函数可以读取这些参数的值,但形参的改变不影响实参) -
返回值:成功返回
0
;失败返回错误码。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;
// 新线程
void *thread_task(void *arg)
{
while (true)
{
printf("我是 %s, 我的进程id: %d\n", (char *)arg, getpid());
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
char *name = new char[32];
snprintf(name, 32, "thread 1");
// 创建线程
pthread_create(&tid, nullptr, thread_task, name);
// 主线程
while (true)
{
cout << "主线程的进程id: " << getpid() << endl;
sleep(1);
}
return 0;
}
非常简单的代码,此时如果直接编译会引发报错
未定义 pthread_create
这个函数。一看就是链接报错,我们使用的是第三方库pthread
,那么在编译的时候必须要指明该库!
解决方法:编译时带上 -lpthread
。
此时再编译就没有问题了
我们可以使用以下命令来查看库的链接情况
ldd 可执行程序
可以看到,原生线程库路径: /lib64/libpthread.so.0
。这足以证明原生线程库确确实实的存在于我们的系统中!
【运行结果】
几乎所有的进程默认都有一个主线程,而主线程主要是执行main
函数中的代码。我们发现:无论是主线程还是新线程,它们的进程id
都是一样的。这也验证了线程是进程的执行分支,只不过一个进程里有两个执行流。
至于主次线程的运行顺序,线程的调度机制源于进程,而多进程运行时,谁先运行取决于调度器,因此主次线程运行的先后顺序不定,具体取决于调度器的调度。
可以通过以下查看正在运行中的线程信息:
ps -aL
#这里的L可以理解为light,表示轻的意思,也就是轻量级进程
解释说明:
PID
字段:表示进程的标识符。主次线程PID
都相等,说明它们隶属同一个进程。LWP
字段:表示轻量级进程(Light Weight Process
)的标识符,本质上就是线程的标识符。我们发现:其中有一个PID
等于LWP
,说明了这个线程是主线程,剩下的是被创建出来的线程。像我们以前写的代码,PID
永远等于LWP
。TTY
字段:指示与线程相关联的终端设备,通常是一个终端(例如,通过SSH
登录的会话)。如果线程没有与任何终端相关联,则该字段可能显示为?
或空白。TIME
:指示进程或线程已经占用CPU
的时间。CMD
:指示启动进程或线程的完整命令。
另外,在Linux
中,进程的多个线程共享同一地址空间。也就说,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到等等。而可以被多个执行流同时调用而不会产生不正确的结果的函数,我们称为可重入函数。
我们可以来验证:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;
const char *symbol = "# ";
void sayhello(const string &words)
{
cout << "你好, 我的名字是" << symbol << words << endl;
}
// 新线程
void *thread_task(void *arg)
{
while (true)
{
sayhello((char *)arg);
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
char *name = new char[<