目录
pthread库
我们知道, 所有的线程, 都属于同一个进程。 所有的线程让他们去打印PID, 那么打印出来的PID最终会是同一个PID。 但是, 我们的线程如果被调度, 那么他就要有一个供别人调用的属于自己的ID。
那么内核当中,有没有很明确的线程的概念呢? 没有, 内核当中只有一个“轻量级进程”的概念。 但是, 并不影响我们的每一个执行流(线程) 都有属于自己的ID, 这个ID叫做tid。
那么, 既然内核中只有“轻量级进程”的概念, 那么他是不是就不会给我们提供线程的系统调用, 只会给我们提供轻量级进程的系统调用!——但是我们用户要使用线程的创建方法, 所以linux程序员, 就在系统和用户层之间开发出了一个pthread线程库。 这个库是在应用层的。是对轻量级进程的相关接口进行封装, 为用户提供直接控制线程的接口。 (几乎所有的linux平台, 都是默认自带pthread库的!linux中编写多线程代码, 需要使用pthread库!)
线程控制
线程创建
pthread_create接口
实验
下面我们使用一下这个接口, 创建一个新线程。
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* threadRoutine(void* args)
{
while (true)
{
cout << "new thread, pid: " << getpid() << endl;
sleep(2);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, nullptr);
while(true)
{
cout << "main thread, pid: " << getpid() << endl;
sleep(1);
}
return 0;
}
那么既然创建的新线程去执行了新的函数, 那么就注定了这两个线程一个线程执行main函数, 访问的是main函数的代码;另一个线程执行threadRoutine函数, 访问的是threadRoutine函数的代码。 然后我们运行结果如下:
上面这是链接式报错。 这是因为我们这里用的接口不是系统调用, 是库方法!!不是c/c++库, 是第三方库。 我们在学习动态库的时候学过, 这里必须我们自己指定链接哪一个库。 那么链接哪一个库呢? 起始man手册里面已经告诉我们了, 如下图:
就是链接这个-pthread库。 我们在makefile中, 链接上这个库:
然后就能编译成功了:
运行后, 我们可以看到打印出来的PID是一样的:
同时, 我们如果使用ps axj也能看到进程也只是一个:
我们想要查到两个执行流, 怎么做呢? 这里有一个选项叫做ps -aL(a表示所有, L可以理解成light)
图中我们画的这个LWP是什么东西呢? 其实, 在我们的linux中, 并没有真正意义上的线程, 是使用的进程模拟的线程。 cpu调度的时候, 不仅仅只看我们的PID, 每一个轻量级进程也有一个自己的标识符, 就是这个LWP(light weight process id)。所以, cpu在调度进程的时候根部看的不是PID, 看的是LWP!!!
另外, 我们发送信号, 如果是给一个线程发, 那么同一个进程内的其他线程同样会挂掉。 就如同下图:
全局变量与多线程
我们创建一个全局变量g_val, 让g_val在主线程进行加加操作。 然后主线程和父线程都对这个g_val进行打印。代码如下:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int g_val = 100;
void* threadRoutine(void* args)
{
while (true)
{
printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, nullptr);
while(true)
{
printf("main thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);
sleep(1);
g_val++;
}
return 0;
}
运行后我们就会发现g_val会逐渐增大!!同时, 新线程也能够看到这个值会变化。 也就是说, 全局变量, 对于所有的线程来说是可见的!
多线程发生异常
如果多线程发生了异常, 不管是哪一个执行流发生异常, 都会导致进程退出。 下面是测试代码(五秒后新线程发生除零错误, 异常, 进程退出):
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int g_val = 100;
void* threadRoutine(void* args)
{
while (true)
{
printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);
sleep(5);
int a = 10;
a /= 0;
// cout << "new thread, pid: " << getpid() << endl;
// show("[new thread]");
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, nullptr);
while(true)
{
printf("main thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);
// cout << "main thread, pid: " << getpid() << ", g_val: " << g_val << ", &g_val: " << &g_val << endl;
// show("[main thread]");
sleep(1);
g_val++;
}
return 0;
}
运行结果:
tid
在上面的实验里面, 我们没有打印过tid。 我们现在以十六进制打印一下这个tid。
然后运行就能看到显然我们的tid和LWP是不一样的。
这是因为LWP是操作系统层面的概念, 作为操作系统自己知道即可, 我们用户并不关心LWP, 我们只关心tid。而这个tid是什么, 其实就是共享区的一块地址(涉及到了底层原理, 后面会讲到)
线程等待
pthread_join接口
而且我们等待线程和等待进程的目的类似, 有两个:
retval
现在我们讨论一下这个第二个参数, 这个第二个参数其实就是拿到我们的新线程的返回值。 我们如果仔细观察就会发现, 我们的的我们的新线程是以回调函数的方式传给pthread_create的, 所以我们无法获得新线程的返回值。 而如果想要拿到这个返回值, 就要用到等待线程时的第二个参数, 这个参数的原理是什么? 下面讲解一下:
线程的终止
pthread_exit接口
首先我们需要知道的是, exit是用来终止进程的, 不能用来终止线程。 如果使用exit终止线程, 会让我们的整个进程都退出。
线程库为我们提供了线程的退出方法:pthread_exit。
参数类似于exit里面的参数。 就是退出码。 也类似于返回值。
下面为简单的代码测试
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<cstdlib>
using namespace std;
void* threadRuntine(void* args)
{
string name = static_cast<char*>(args);
int cnt = 5;
while (cnt--)
{
cout << args << " say#: " << "I am a new thread" << endl;
sleep(1);
}
pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRuntine, (void*)"thread 1");
void* ret;
pthread_join(tid, &ret);
cout << (long long int)ret << endl;
return 0;
}
pthread_cancel接口
这个函数的作用是给新线程发送一个取消请求, 并且退出的线程, 退出码为-1。(注意, 不常用)
void* threadRuntine(void* args)
{
string name = static_cast<char*>(args);
int cnt = 5;
while (cnt--)
{
cout << args << " say#: " << "I am a new thread" << endl;
sleep(1);
}
pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRuntine, (void*)"thread 1");
//两秒后就直接退出
sleep(2);
pthread_cancel(tid);
//
void* ret;
pthread_join(tid, &ret);
cout << (long long int)ret << endl;
return 0;
}
使用自定义类作为线程接收对象
我们可以使用自定义类型作为线程的接收对象。
我们使用一下自定义类型的对象, 实验过程为: 首先定义两个类, 一个Request, 一个Response。 其中Request的成员变量有一个start, 一个end。 我们新线程就是来计算从start到end的总和。 然后就是Response, Response用来新线程的返回。里面的成员包含一个_result, 一个_exitcode。
#include<iostream>
#include<unistd.h>
#include<string>
#include<cstdlib>
#include<pthread.h>
using namespace std;
class Request
{
public:
Request(int start, int end, string threadname)
:_start(start)
,_end(end)
,_threadname(threadname)
{}
public:
int _start;
int _end;
string _threadname;
};
class Response
{
public:
Response(int result, int exitcode)
:_result(result)
,_exitcode(exitcode)
{}
public:
int _result;
int _exitcode;
};
void* threadRuntine(void* args)
{
Response* rsp = new Response(0, 0);
Request* req = static_cast<Request*>(args);
for (int i = req->_start; i <= req->_end; i++)
{
rsp->_result += i;
usleep(100000);
cout << ".exe is running..." << i << endl;
}
return (void*)rsp;
}
int main()
{
Request* req = new Request(1, 100, "thread 1");
pthread_t tid;
pthread_create(&tid, nullptr, threadRuntine, (void*)req);
void* ret;
pthread_join(tid, &ret);
cout << "result: " << static_cast<Response*>(ret)->_result << endl;
return 0;
}
运行结果:
线程库的底层原理
我们理解线程库的大概底层原理, 其实只要理解下面博主画的这张图即可:
线程栈
——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!