
线程控制函数
一、线程函数
1、线程ID
获取线程ID,线程ID的名字叫做tid
c
#include <pthread.h>
pthread_t pthread_self(void);
返回值:该函数返回调用它的线程的线程tid,返回类型为pthread_t
cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <thread>
#include <string>
using namespace std;
//将数字转为十六进制
string toHex(int num)
{
char ret[64];
snprintf(ret, sizeof(ret), "%p", num);
return ret;
}
//循环打印tid
void *threadroutine(void *args)
{
while(true)
{
sleep(2);
cout << "thread id: " << toHex(pthread_self()) << endl;
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadroutine, nullptr);
while(true)
{
cout << "creat a new thread, id: " << toHex(tid) << endl;
sleep(1);
}
pthread_join(tid, nullptr);
return 0;
}

2、线程等待
pthread_join
:用于等待线程
c
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
返回值:成功返回0,失败返回错误码
thread
:表示要等待的目标线程的线程tid
retval
:用于存储目标线程的退出状态,如果不需要获取退出状态,可以将其设置为 NULL
,目标线程的退出状态是通过调用pthread_exit
函数或者从线程函数中返回得到的
3、线程终止
pthread_exit
用于终止线程
c
#include <pthread.h>
void pthread_exit(void *retval);
retval
:这是一个 void*
类型的指针,用于传递线程的退出状态,这个值可以被其他线程通过 pthread_join
函数获取,如果线程不需要返回任何状态信息,可将其设置为NULL
需要注意的是,这里的参数是指针,不要指向一个局部变量,这里的栈空间在函数结束后会被系统收回
4、线程取消
pthread_cancel
用于向指定线程发送取消请求
c
#include <pthread.h>
int pthread_cancel(pthread_t thread);
返回值:成功返回0,失败返回错误码
thread
:表示要发送取消请求的目标线程的线程tid

5、线程分离
c
#include <pthread.h>
int pthread_detach(pthread_t thread);
返回值:成功返回0,失败返回非零错误码
thread
:要分离的线程tid
在默认情况下,新创建的线程是可被等待的,线程退出后,需要对其进行线程等待,否则无法释放资源,造成系统泄漏,但如果我们并不关心线程的返回值,如我们仅用它完成一个动作,但我们不需要它的返回值,可被等待的性质就成为了一种负担,这个时候我们可以通过分离线程,在该线程退出的时候自动释放线程资源
6、pthread线程库的理解
在一些操作系统的早期版本中,内核主要以进程作为基本的调度和资源分配单位,从这个角度可以说在早期内核中没有像现在这样清晰明确的线程概念 ,而线程概念在用户层面上,pthread
库为我们提供了一系列用于线程操作的接口,极大地方便了开发者对线程的管理和使用
但在现代操作系统的内核中,是有线程相关概念和实现的,内核支持将线程作为独立的调度实体进行管理,同时,线程的实现往往与轻量级进程紧密相关,轻量级进程在内核中拥有类似进程的一些数据结构,如进程控制块task_struct
等,线程在底层很多时候是基于轻量级进程来实现的,以便在用户空间获得更好的并发效果和在内核空间获得资源调度等方面的支持,所以pthread库为用户提供了便捷的线程管理接口,而内核中线程概念以轻量级进程等形式存在并进行实际的调度和管理等操作

除了主线程,其他线程都存在于堆栈之间的内存映射段,其中有动态库,动态库中有pthread.so
动态库,动态库中的方法通过调用内核来维护TCB结构体
我们将线程管理起来也是根据先描述后组织的方法进行的,线程的控制块叫做TCB(thread control block),线程的tid
在现行的NPTL(原生POSIX线程库)标准下我们可以简单的把它当做是线程控制块的地址,因为每个TCB的地址不同,所以它们的tid
也不同,实际上如果我们用打印的方式是验证不了这个问题的,因为程序在链接时一定会链接一个叫做ld
的动态库,这个库可以修饰虚拟地址(见下图最后一行)
除了主线程,所有的其他线程的独立栈,默认都在共享区pthread.so
库中
从图中我们也清楚地表达了每个线程都有自己的局部存储和独立的栈结构,后面我们会来证明的
二、线程控制拓展
1、C++11线程库
c++11的线程库thread
实际上是封装了原生线程库pthread.h
,在Linux操作系统下,c++11底层封装的是Linux的系统调用,在Windows操作系统下,c++11封装的是Windows的系统调用,所以语言具有可移植性,因为语言库是通过条件编译先判断该系统是什么系统,然后对于不同的操作系统选择包含不同的API头文件
2、每个线程都有自己独立的栈结构
线程函数的参数和返回值可以传递类对象,在这里我们通过传递类对象来验证每个进程都有自己独立的栈结构
cpp
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>
using namespace std;
//要创建的线程数量
#define NUM 3
//定义一个可扩展类ThreadInfo
class ThreadInfo
{
public:
ThreadInfo(const string &threadname)
:threadname_(threadname)
{}
public:
string threadname_;
};
//tid转十六进制
string toHex(pthread_t tid)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "%p", tid);
return buffer;
}
//
void *threadroutine(void *args)
{
int i = 0;
int num = 0;
ThreadInfo *ti = static_cast<ThreadInfo*>(args);
//线程循环,每次打印线程名称、线程ID、进程ID、变量num以及num地址
while(i < 10)
{
cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << ", num: " << num << ", &num: " << toHex((pthread_t)&num) << endl;
i++;
num++;
usleep(10000);
}
delete ti;
return nullptr;
}
int main()
{
vector<pthread_t> tids;
for(int i = 0; i < NUM; i++)
{
pthread_t tid;
ThreadInfo *ti = new ThreadInfo("Thread-"+to_string(i));
pthread_create(&tid, nullptr, threadroutine, ti);
tids.push_back(tid);
usleep(1000);
}
//线程等待
for(auto tid:tids)
{
pthread_join(tid, nullptr);
}
return 0;
}

打印出来后我们可以发现tid相同的情况下,num地址相同,tid不同的情况下,num地址也不同,证明每个线程都有自己独立的栈结构
虽然线程都有自己独立的栈结构,但是它们之间如果想要通信也可以不搭建信道,因为只要定义一个全局的指针变量,就可以通过这个指针指向任何线程独立栈结构中的变量,所以说到底,线程还是在进程中的,它们之间的耦合性特别强
今日分享就到这了~
