目录
【1】引入如何看待地址空间和列表
【2】什么是线程
【3】线程的优点
【4】线程的缺点
【5】线程异常
【6】线程用途
【7】线程VS进程
【8】Linux线程控制
【8.1】查看轻量级线程指令
【8.2】线程创建
【8.2.1】POSIX线程库
【8.2.2】创建线程
【8.2.3】一次性创建一个线程
【8.2.4】一次性创建多个线程
【8.2】线程终止
【8.3】线程取消
【8.4】线程等待
【8.5】分离线程
【9】线程ID及进程地址空间布局
【1】引入如何看待地址空间和列表
-
地址空间是进程能看到的资源窗口。
-
页表决定,进程真正拥有资源的情况。
-
合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有的资源进行分类。
【2】什么是线程
-
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
-
线程是进程内的一个执行流。
-
一切进程至少都有一个执行线程。
-
线程在进程内部运行,本质是在进程地址空间内运行。
-
在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化。
-
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
因为我们可以通过虚拟地址空间+页表的方式对进程进行资源划分,单个“进程”执行力度,一定要比之前的进程要细!
【思考】如果我们OS真的要专门设计“线程”概念,OS未来要不要管理这个线程呢?
一定要为线程设计专门的数据结构,表示线程对象---称为TCB(Windows是这样的)。
单纯从线程调度角度,线程和进程有很多地方是重叠的,所有Linux工程师,不想给Linux“线程”专门设计对应的数据结构,而是直接复用PCB,用PCB用来表示Linux内部的“线程”。
线程是在进程内部运行的,线程在进程的地址空间内运行,拥有进程的一部分资源!
思考:线程来了,问题也随之而来!
-
什么是进程呢?
之前:进程 = 内核数据结构 + 进程对应的代码和数据。
现在:从内核角度,进程分配系统资源的实体。
-
什么是线程呢?
现在:CPU调度的基本单位。
-
如何看待我们之前学习进程时,对应的进程概念呢?和今天的线程冲突吗?
之前:承担系统资源的基本实体,只不过内部只有一个执行流!
现在:一个进程内部可以有多个执行流!
-
站在CPU的角度
之前:调用进程
现在:调用进程里的一个分支
但是:CPU并不关心,今天喂给CPU的task_struct <= 历史的tesk_struct的含义
思考:综合以上所总结Linux内核中有没有真正意义的线程呢?
没有!,Linux是用进程PCB来模拟线程,是一种完全属于自己的一套线程方案,站在CPU的视角,每一个PCB,都可以称之为叫做轻量级进程,Linux*线程是CPU调度的基本单位,而进程是承担分配系统资源的基本单位,进程用来整体申请资源,线程用来伸手向进程要资源,Linux中没有真正意义的线程,
【3】线程的优点
-
创建一个新线程的代价要比创建一个新进程小得多。
-
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
-
进程:切换页表&&虚拟地址空间&&切换PCB&&上下文切换。
-
线程:切换PCB&&上下文切换。
-
线程切换cache不用
-
线程占用的资源要比进程少很多。
-
能充分利用多处理器的可并行数量。
-
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
-
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
-
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
【4】线程的缺点
-
性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
-
健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
-
缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
-
编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。
由于Linux中没有真正意义的线程 -》而OS只认线程,用户(程序员)也只认线程,Linux边无法直接提供创建线程的系统调用接口,而只能给我们提供创建轻量级进程的接口!
【5】线程异常
-
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
-
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
【6】线程用途
-
合理的使用多线程,能提高CPU密集型程序的执行效率。
-
合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。
【7】线程VS进程
-
进程是资源分配的基本单位
-
线程是调度的基本单位
-
线程共享进程数据,但也拥有自己的一部分数据:
-
线程ID
-
一组寄存器
-
栈
-
errno
-
信号屏蔽字
-
调度优先级
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
-
文件描述符表
-
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
-
当前工作目录
-
用户id和组id
【最理想的创建合理性】 CPU的核数决定线程的个数,CPU的个数决定进程的个数。
【8】Linux线程控制
【8.1】查看轻量级线程指令
// 查看轻量级进程指令
ps -aL
【8.2】线程创建
【8.2.1】POSIX线程库
-
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
-
要使用这些函数库,要通过引入头文<phtread.h>。
-
链接这些线程函数库时要使用编译器命令的“-lpthread”选项。
【8.2.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;失败返回错误码
【错误检查】
-
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
-
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回。
-
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小。
【实例代码】
// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -lpthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
/* 线程函数 */
void* thread_rotine(void* args) {
const char* buffer = (const char*)args;
while(true) {
cout << "我是新线程,我正在运行,我的名字叫做:" << buffer << endl;
sleep(1);
}
}
/* 入口函数 */
int main() {
// 线程的描述符.
pthread_t tid;
// 参数传递依次是:描述符 nullptr 线程函数 传送的参数
pthread_create(&tid, nullptr, thread_rotine, (void*)"thread_one");
// 主线程:
while(true) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
cout << "我是主线程,我正在运行,我的tid是:" << buffer << endl;
sleep(1);
}
return 0;
}
// 打印结果:
我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one
我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one
我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one
我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one
【概念】 线程一旦被创建,几乎所有的资源都是被所有线程共享的!
// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -lpthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
/* 测试共享的全局变量和函数 */
int g_num = 0;
const string Func() {
return "我是一个独立的函数!";
}
/* 线程函数 */
void* thread_rotine(void* args) {
const char* buffer = (const char*)args;
while(true) {
cout << "我是新线程,我正在运行,我的名字叫做:" << buffer << " " << Func() << " " << g_num++ << endl;
sleep(1);
}
}
/* 入口函数 */
int main() {
// 线程的描述符.
pthread_t tid;
// 参数传递依次是:描述符 nullptr 线程函数 传送的参数
pthread_create(&tid, nullptr, thread_rotine, (void*)"thread_one");
// 主线程:
while(true) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
cout << "我是主线程,我正在运行,我的tid是:" << buffer << " " << Func() << " " << g_num << endl;
sleep(1);
}
return 0;
}
// 打印结果:现象就是线程在进程中的资源共享
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 2
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 3
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 3
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 4
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 4
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 4
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 5
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 5
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 6
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 6
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 7
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 7
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 8
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 8
【概念】线程也一定有自己私有的资源,什么资源应该是线程私有的呢?
-
PCB属性私有。
-
要有一定私有上下文结构。
-
每一个线程都要有自己独立的栈结构。
【概念】线程与进程之间相对比,线程之间的切换需要操作系统做的工作要少很多。
-
进程:切换页表 && 虚拟地址空间 && 切换PCB && 上下文切换。
-
线程:切换PCB && 上下文切换。
【8.2.3】一次性创建一个线程
// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -lpthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
/* 创建的新线程 */
void* Start_Routine(void* args) {
// 将参数转换为有效参数
const string paramName = static_cast<const char*>(args);
// 创建的线程执行
while(true) {
cout << "new thread create success, name:Start_Routine " << endl;
sleep(1);
}
}
/* 程序入口函数 */
int main() {
// 创建线程
pthread_t tid;
pthread_create(&tid, nullptr, Start_Routine, (void*)"Thread One");
// 主线程执行
while(true) {
cout << "new thread create success, name:main thread" << endl;
sleep(1);
}
return 0;
}
// 打印结果:
new thread create success, name:Start_Routine
new thread create success, name:main thread
new thread create success, name:Start_Routine
new thread create success, name:main thread
new thread create success, name:Start_Routine
new thread create success, name:main thread
【8.2.4】一次性创建多个线程
// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -lpthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》
// 监控脚本 while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData {
public:
pthread_t tid; // 线程的描述符
char nameBuffer[64]; // 线程的名称
};
/* 抽象的线程函数 */
// start_routine函数现在是被几个线程执行呢? 10
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void*Start_Routine(void* args) {
sleep(1);
// 获取到传递的void*参数
// 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
ThreadData* td = static_cast<ThreadData*>(args);
// int cnt = 10; // 这里的操作是为了演示线程具有独立的栈结构!
while(true) {
// cout << "cnt:" << &cnt << endl;
cout << "new thread create success,name:" << td->nameBuffer << " " << td->tid << endl;
sleep(5);
}
delete td;
return nullptr;
}
/* 入口函数 */
int main() {
vector<ThreadData*> threads;
#define NUM 10 // 宏定义,默认一次性创建10个进程
// 一次性创建多个进程
for(int i = 0; i < NUM; i++) {
// 在堆中创建线程的单集合
ThreadData* td = new ThreadData();
// 封装ThreadData类
snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i + 1);
pthread_create(&td->tid, nullptr , Start_Routine, td);
// 保留每个线程的信息
threads.push_back(td);
}
// 打印已创建出来的线程清单
for(auto v : threads) {
cout << "create thread:" << v->nameBuffer << " " << v->tid << " success!" << endl;
}
// 主线程执行
while(true) {
cout << "new thread create success,name:main tread!" << endl;
sleep(5);
}
return 0;
}
// 打印结果:
create thread:thread,1 139744236070656 success!
create thread:thread,2 139744227677952 success!
create thread:thread,3 139744219285248 success!
create thread:thread,4 139744210892544 success!
create thread:thread,5 139744202499840 success!
create thread:thread,6 139744194107136 success!
create thread:thread,7 139744185714432 success!
create thread:thread,8 139744177321728 success!
create thread:thread,9 139744168929024 success!
create thread:thread,10 139744160536320 success!
new thread create success,name:main tread!
new thread create success,name:thread,7 140342020540160
new thread create success,name:thread,5 140342037325568
new thread create success,name:thread,2 140342062503680
new thread create success,name:thread,1 140342070896384
new thread create success,name:thread,10 140341995362048
new thread create success,name:thread,6 140342028932864
new thread create success,name:thread,8 140342012147456
new thread create success,name:thread,9 140342003754752
new thread create success,name:thread,3 140342054110976
new thread create success,name:thread,4 140342045718272
【8.2】线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
-
从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
-
线程可以调用pthread_ exit终止自己。
-
一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
【pthread_exit函数】 |【return nullptr】
// 功能:线程终止
// 原型
void pthread_exit(void *value_ptr);
// 参数
// value_ptr:value_ptr不要指向一个局部变量。
// 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
// 【注意】需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc
// 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
【测试代码】
// 监控脚本
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
/* 线程数据类 */
class ThreadData{
public:
pthread_t tid;
char nameBuffer[64];
};
// start_routine函数现在是被几个线程执行呢? 10
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
// 演示打印!
sleep(1);
// 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
ThreadData *td = static_cast<ThreadData*>(args); // 安全的进行强制类型转换。
// exit(-1); // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
// return nullptr; // ok
pthread_exit(nullptr); // oK
int cnt = 10;
char tempStr[64] = {'\0'};
while(cnt){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
cout << tempStr;
sleep(1);
}
// 释放空间
delete td;
return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
}
int main(){
vector<ThreadData *> tids;
#define NUM 10
// 一次性创建多个进程.
for(int i = 1; i <= NUM; i++){
// 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
ThreadData* td = new ThreadData();
snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
pthread_create(&td->tid, nullptr, start_routine, td);
// 每个进程进行保存!
tids.push_back(td);
}
// 打印线程清单
for(auto& e : tids){
cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
}
char tempStr[64] = {'\0'};
while(true){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
cout << tempStr;
sleep(1);
}
return 0;
}
【8.3】线程取消
// 注意:线程可以被取消,线程要取消,前提是这个线程已经跑起来了!
// 功能:取消一个执行中的线程
// 原型
int pthread_cancel(pthread_t thread);
// 参数
// thread:线程ID
// 返回值:成功返回0;失败返回错误码
【测试代码】
// 监控脚本
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
/* 线程数据类 */
class ThreadData{
public:
long long number;
pthread_t tid;
char nameBuffer[64];
};
// start_routine函数现在是被几个线程执行呢? 10
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
// 演示打印!
sleep(1);
// 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
ThreadData *td = static_cast<ThreadData*>(args); // 安全的进行强制类型转换。
// exit(-1); // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
// return nullptr; // ok
int cnt = 10;
char tempStr[64] = {'\0'};
while(cnt){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
cout << tempStr;
sleep(1);
}
pthread_exit((void*)td->number); // oK
// return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
}
int main(){
vector<ThreadData *> tids;
#define NUM 10
// 一次性创建多个进程.
for(int i = 1; i <= NUM; i++){
// 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
ThreadData* td = new ThreadData();
td->number = i;
snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
pthread_create(&td->tid, nullptr, start_routine, td);
// 每个进程进行保存!
tids.push_back(td);
}
// 打印线程清单
for(auto& e : tids){
cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
}
// 等待所有的线程
// for(auto& e : tids){
// int n = pthread_join(e->tid, (void **)&e->number);
// assert(n == 0);
// cout << "join:" << e->nameBuffer << " success!" << " number: " << (long long)e->number << endl;
// // 等待一个,就释放一个线程空概念
// delete e;
// }
// 取消线程
// 线程如果是要被取消,退出码为-1(PTHREAD_CANCELED)
// 5秒后取消一般的线程
sleep(5);
for(int i = 0; i<tids.size() / 2; i++){
pthread_cancel(tids[i]->tid);
cout << "pthread_cancel: " << tids[i]->number << " success!" << endl;
}
char tempStr[64] = {'\0'};
while(true){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
cout << tempStr;
sleep(1);
}
return 0;
}
【8.4】线程等待
线程也是需要等待的,线程为什么需要线程等待?
线程也是需要等待的,如果不等待,会造成类似僵尸进程的问题 - 内存泄漏!
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,创建新的线程不会复用刚才退出线程的地址空间。
// 功能:等待线程结束
// 原型
int pthread_join(pthread_t thread, void **value_ptr);
// 参数
// thread:线程ID
// value_ptr:它指向一个指针,后者指向线程的返回值,用来获取线程函数结束后,的执行见过
// 返回值:成功返回0;失败返回错误码
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
-
如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
-
如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
-
如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
-
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
// Makefile文件 》》》》》》》》》》》》》》》
myThread:myThread.cpp
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f myThread
// myThread.cpp文件》》》》》》》》》》》》》
// 监控脚本
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
/* 线程数据类 */
class ThreadData{
public:
pthread_t tid;
char nameBuffer[64];
};
// start_routine函数现在是被几个线程执行呢? 10
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
// 演示打印!
sleep(1);
// 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
ThreadData *td = static_cast<ThreadData*>(args); // 安全的进行强制类型转换。
// exit(-1); // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
// return nullptr; // ok
int cnt = 10;
char tempStr[64] = {'\0'};
while(cnt){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
cout << tempStr;
sleep(1);
}
pthread_exit(nullptr); // oK
// return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
}
int main(){
vector<ThreadData *> tids;
#define NUM 10
// 一次性创建多个进程.
for(int i = 1; i <= NUM; i++){
// 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
ThreadData* td = new ThreadData();
snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
pthread_create(&td->tid, nullptr, start_routine, td);
// 每个进程进行保存!
tids.push_back(td);
}
// 打印线程清单
for(auto& e : tids){
cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
}
// 等待所有的线程
for(auto& e : tids){
int n = pthread_join(e->tid, nullptr);
assert(n == 0);
cout << "join:" << e->nameBuffer << " success!" << endl;
// 等待一个,就释放一个线程空概念
delete e;
}
char tempStr[64] = {'\0'};
while(true){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
cout << tempStr;
sleep(1);
}
return 0;
}
【如何获取线程自定义退出码】
// 监控脚本
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
/* 线程数据类 */
class ThreadData{
public:
long long number;
pthread_t tid;
char nameBuffer[64];
};
// start_routine函数现在是被几个线程执行呢? 10
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
// 演示打印!
sleep(1);
// 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
ThreadData *td = static_cast<ThreadData*>(args); // 安全的进行强制类型转换。
// exit(-1); // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
// return nullptr; // ok
int cnt = 10;
char tempStr[64] = {'\0'};
while(cnt){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
cout << tempStr;
sleep(1);
}
pthread_exit((void*)td->number); // oK
// return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
}
int main(){
vector<ThreadData *> tids;
#define NUM 10
// 一次性创建多个进程.
for(int i = 1; i <= NUM; i++){
// 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
ThreadData* td = new ThreadData();
td->number = i;
snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
pthread_create(&td->tid, nullptr, start_routine, td);
// 每个进程进行保存!
tids.push_back(td);
}
// 打印线程清单
for(auto& e : tids){
cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
}
// 等待所有的线程
for(auto& e : tids){
int n = pthread_join(e->tid, (void **)&e->number);
assert(n == 0);
cout << "join:" << e->nameBuffer << " success!" << " number: " << (long long)e->number << endl;
// 等待一个,就释放一个线程空概念
delete e;
}
char tempStr[64] = {'\0'};
while(true){
snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
cout << tempStr;
sleep(1);
}
return 0;
}
【问题】为什么没有看到,线程退出的时候,对应的退出信号?
线程退出,收到信号,整个进程都会退出,pthread_join:默认就认为函数会调用成功,不考虑异常的问题,异常问题就是你进程考虑的问题。
【8.5】分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
【引入函数】
#include <pthread.h>
// 功能:获取线程id
// 原型
pthread_t pthread_self(void);
【代码实例】
// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
myThread:myThread.cpp
g++ -o $@ $^ -std=c++11 -l pthread
.PHONY:clean
clean:
rm -f myThread
// myThread.cpp文件》》》》》》》》》》》》》》》》》》》
#include <iostream>
#include <cassert>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using std::cout;
using std::cin;
using std::endl;
std::string change_id(const pthread_t &thread_id){
char tid[128];
snprintf(tid, sizeof(tid), "0x%x", thread_id);
return tid;
}
// 线程方法调用.
void *start_routine(void *args){
sleep(1);
std::string threadName = static_cast<const char*>(args);
while(true){
cout << threadName << " running.... : " << change_id(pthread_self()) << endl;
sleep(1);
}
}
int main(){
// 创建线程.
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread One");
assert(n == 0); (void)n;
// 打印创建的pthread_t的线程id只
cout << "main thread id : " << change_id(pthread_self()) << endl;
cout << "main thread running ... new thread id: " << change_id(tid) << endl;
// 等待线程.
pthread_join(tid, nullptr);
return 0;
}
[shaxiang@VM-8-14-centos threads_02]$ ./myThread
main thread id : 0xaf078740
main thread running ... new thread id: 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
【可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离】
#include <pthread.h>
// 功能:线程分离
// 原型
int pthread_detach(pthread_t thread);
【代码实例】
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
using std::cout;
using std::cin;
using std::endl;
std::string change_id(const pthread_t &thread_id){
char tid[128];
snprintf(tid, sizeof(tid), "0x%x", thread_id);
return tid;
}
// 线程方法调用.
void *start_routine(void *args){
sleep(1);
std::string threadName = static_cast<const char*>(args);
int cnt = 5;
while(cnt--){
cout << threadName << " running.... : " << change_id(pthread_self()) << endl;
sleep(1);
}
}
int main(){
// 创建线程.
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread One");
assert(n == 0); (void)n;
// 将线程进行分离
pthread_detach(tid);
// 打印创建的pthread_t的线程id只
cout << "main thread id : " << change_id(pthread_self()) << endl;
cout << "main thread running ... new thread id: " << change_id(tid) << endl;
// 等待线程 -> 如果线程默认是joinable的,如果设置了分离状态,不能够进行等待了!
int ret = pthread_join(tid, nullptr);
cout << "result: " << ret << " : " << strerror(ret) << endl; // ret失败了!
return 0;
}
// 打印结果
[shaxiang@VM-8-14-centos threads_02]$ ./myThread
main thread id : 0xac09b740
main thread running ... new thread id: 0xab006700
result: 22 : Invalid argument // 非法参数,因为线程被分离了!
【9】线程ID及进程地址空间布局
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
#include <pthread.h>
// 功能:获取线程id
// 原型
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
【线程局部存储】
【局部存储测试 int g_value = 100】
// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -l pthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int g_value = 100;
string ChangeId(const pthread_t& tid) {
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
return buffer;
}
void* StartRoutine(void* args) {
sleep(2);
const char* msg = static_cast<const char*>(args);
while(true) {
cout << msg << " Thread Id: " << ChangeId(pthread_self())\
<< "g_value: " << g_value << " &g_value: " << &g_value << endl;
g_value++;
sleep(1);
}
pthread_exit(nullptr);
}
int main() {
// 创建线程,并且将线程分离
pthread_t tid;
int n = pthread_create(&tid, nullptr, StartRoutine, (void*)"One");
assert(n == 0); (void)n;
// 打印创建的pthread_t的线程id
while(true) {
cout << "Main Thread Id: " << ChangeId(pthread_self())\
<< "g_value: " << g_value << " &g_value: " << &g_value << endl;
sleep(1);
}
return 0;
}
【局部存储测试 __threadint g_value = 100】
// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -l pthread
.PHONY:clean
clean:
rm -rf myThread
// Thread.cc文件》》》》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;
__thread int g_value = 100;
string ChangeId(const pthread_t& tid) {
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
return buffer;
}
void* StartRoutine(void* args) {
sleep(2);
const char* msg = static_cast<const char*>(args);
while(true) {
cout << msg << " Thread Id: " << ChangeId(pthread_self())\
<< "g_value: " << g_value << " &g_value: " << &g_value << endl;
g_value++;
sleep(1);
}
pthread_exit(nullptr);
}
int main() {
// 创建线程,并且将线程分离
pthread_t tid;
int n = pthread_create(&tid, nullptr, StartRoutine, (void*)"One");
assert(n == 0); (void)n;
// 打印创建的pthread_t的线程id
while(true) {
cout << "Main Thread Id: " << ChangeId(pthread_self())\
<< "g_value: " << g_value << " &g_value: " << &g_value << endl;
sleep(1);
}
return 0;
}
【10】基于原声线程库封装线程类
【Makefile文件】
# 定义替代关系
cc=g++
standard=-std=c++11
# 定义myThread可执行依赖于Thread.cc文件
myThread:Thread.cc
$(cc) -o $@ $^ $(standard) -l pthread
# 定义删除可执行命令
.PHONY:clean
clean:
rm -rf myThread
【Thread.hpp文件】
#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
#include <cassert>
/* 连接上下文 */
class Thread;
class ConnectText {
public:
/* 构造函数 */
ConnectText() : _this(nullptr), _args(nullptr) {}
/* 析构函数 */
~ConnectText() {}
public:
Thread* _this;
void* _args;
};
/* 线程类 */
class Thread {
public:
using func_t = std::function<void*(void*)>; // // 从定义类似函数指针类型:返回值是:void* 参数是:void*
public:
/* 构造函数 */
Thread(const func_t& func, void* args, const int number)
: _func(func), _args(args)
{
// 创建线程的名称
char buffer[64];
snprintf(buffer, sizeof(buffer), "Thread-%d", number);
_tName = buffer;
// 创建线程后,启动
ConnectText* cnt = new ConnectText();
cnt->_this = this;
cnt->_args = _args;
int n = pthread_create(&_tid, nullptr, StartRoutine, (void*)cnt);
assert(n == 0); (void)n;
// 编译debug的方式时assert是存在的,release方式assert是不存在的,到时n就是定义了,但是没有被使用的变量。
// 在有的编译器下会有warning。
}
/* 析构函数 */
~Thread(){}
public:
/* 线程函数 */
static void* StartRoutine(void* args) { // 在类内创建线程,想让线程执行对应的方法,需要将方法设置称为static
ConnectText* cnt = static_cast<ConnectText*>(args);
void* exRet = cnt->_this->Run(cnt->_args);
delete cnt;
return exRet;
}
public:
/* 线程等待 */
void Join() {
int n = pthread_join(_tid, nullptr);
assert(n == 0); (void) n;
}
private:
void* Run(void* args) {
return _func(args);
}
private:
pthread_t _tid; // 线程id
std::string _tName; // 线程名称
func_t _func; // 线程函数
void* _args; // 线程参数
};
【Thread.cc文件】
#include <memory>
#include <unistd.h>
#include "Thread.hpp"
using namespace std;
// 监控命令:while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
void* StartRoutine(void* args) {
const char* msg = static_cast<const char*>(args);
while(true) {
cout << "我是一个线程,我正在执行任务:" << msg << endl;
sleep(1);
}
return nullptr;
}
int main() {
unique_ptr<Thread> thread1(new Thread(StartRoutine, (void*)"下载任务", 1));
thread1->Join();
return 0;
}
【打印结果】
[shaxiang@VM-8-14-centos Thread_Packaging]$ ./myThread
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务
我是一个线程,我正在执行任务:下载任务