在Linux系统中,线程是指在同一个进程中并发执行的多个执行序列。每个线程都有自己的程序计数器、寄存器集合、栈空间、线程特有数据等,但它们共享同一个进程的地址空间和其他资源。Linux系统中的线程是由内核进行调度和管理的,因此线程之间的切换是由内核来控制的。 以下是一些关于Linux线程的重要概念:
在
fork()
创建的子进程中,子进程会从fork()
调用的位置开始执行,继续执行父进程的代码。父进程和子进程在fork()
调用之后会并发执行,每个进程有自己独立的执行流,它们可以独立地执行不同的任务或者并发执行相同的任务。 因此,子进程可以被看作是父进程的一个执行流,它继承了父进程的一部分代码和状态,但是拥有自己独立的执行环境
但是,每次创建子进程都要复制父进程的地址空间、代码段、数据段等资源,然后将子进程的执行流独立地运行起来。这代价也不小。所以线程的优势便体现出来:创建一个新线程的代价要比创建一个新进程小得多
task_struct现在是一个个的线程了
正文:代码段(区),我们的代码在进程中,全部都是串行调用的 地址空间和地址空间上的虚拟地址,本质是一种"资源 将不同的代码段分配给不同的线程,使得线程能够并发执行,从而将串行执行转变为并发执行。通过将不同的代码段分配给不同的线程,并发执行可以充分利用多核处理器的性能,提高系统的并发能力
线程在进程的地址空间内运行
首先我们来设想一下:让我们自己来设计线程怎么设计
但是我们也想到这些逻辑,我们在设计进程时已经设计过了
Linux的设计者认为,进程和线程都是执行流,具有极度的相似性,没必要单独设计数据结构和算法直接复用代码 使用进程来模拟线程,所以Linux中没有真正意义上的线程,采用了巧妙的复用
进程的内核角度:承担分配系统资源的基本实体
线程:就是一个一个执行流,是进程内的执行分支
从内核角度来看,进程是操作系统分配系统资源的基本实体,包括内存空间、文件描述符、CPU时间等。每个进程都有自己的地址空间和资源,是操作系统中最基本的执行单位。
而线程则是进程内的执行流,是进程的一个执行分支。线程共享进程的地址空间和资源,但拥有独立的执行流和栈空间。线程之间可以更快地进行通信和数据共享,同时也能更高效地实现并发执行。
在操作系统中,进程和线程的关系可以理解为:一个进程可以包含一个或多个线程,每个线程都是进程的一部分,共享进程的资源。进程和线程之间的关系是一种包含和被包含的关系,进程是线程的容器,线程是进程的执行单元。
在Linux系统中,所有的执行流都被称为轻量级进程(Lightweight Process,LWP),实际上就是操作系统概念中的线程。在Linux中,线程和进程的区别并不是很明显,因为Linux将线程实现为与进程相似的实体,即轻量级进程。
在Linux中,每个轻量级进程(线程)都对应一个task_struct
结构体,操作系统通过调度算法选择下一个要执行的轻量级进程,而不关心这个task_struct
属于哪个进程,或者是属于一个进程的其中一个线程。
因此,在Linux中,CPU调度的实际执行单元是轻量级进程(线程),而不是进程。每个轻量级进程都有自己的执行流,可以独立执行代码,拥有独立的栈空间和寄存器状态。多个轻量级进程可以共享一个进程的资源,实现并发执行。
在Linux中,所有的执行流都被称为轻量级进程(线程),操作系统通过调度算法选择下一个要执行的轻量级进程,而不关心它们属于哪个进程。这种设计使得线程和进程之间的切换更加高效,同时也方便了并发编程和多任务处理。
操作系统进行内存管理的基本单位是4KB 内存里都是以4kb大小分的一个一个内存块——空间 可执行程序也是以4kb进行分——内容
现在我们再来重新看待页表:
后12位叫做叶内偏移 同时读取时还要结合数据类型的大小
给不同的线程分配不同的区域,本质就是给让不同的线程,各自看到全部页表的子集
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
using namespace std;
void *test(void *arg)
{
while (true)
{
std::cout << "I am new thread, pid: " << getpid() << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, test, nullptr);
while (true)
{
std::cout << "I am main thread, pid: " << getpid() << std::endl;
sleep(1);
}
return 0;
}
函数编译完成,是若干行代码块,函数名是该代码块的入口地址。每一行代码都有地址 -----虚拟地址(逻辑地址) 最终形成的可执行程序中的所有函数,都会按照地址空间统一编址。这意味着每个函数在虚拟地址空间中都有自己的地址范围
本质就是:代码按照不同的函数,把程序分为不同的区域。每一个执行流执行的是该函数对应的代码区域
ps -aL
是 ps
命令的一种用法,用于显示系统中的所有进程的线程信息。在这个命令中,选项 -a
表示显示所有进程(包括其他用户的进程),而选项 -L
表示显示进程的线程信息。
首先我们知道Linux系统下是没有真正的线程的,只有轻量级进程。那么Linux系统,不会有线程相关的系统调用,只有轻量级进程的系统调用 但是普通用户不知道这件事,当其他系统的用户来使用Linux时就会默认为Linux没有线程(轻量级进程:明明我就是) 所以Linux就提供了一个库:pthread库(原生线程库,Linux系统提供)——将轻量级进程的系统调用进行封装,转成线程相关的接口语义提供给用户
我们g++编译器是会默认链接 C++ 标准库。因此,当您使用
std::vector
等,编译器会自动链接 C++ 标准库,无需显式指定-lc
而在使用pthread
时需要指定-lpthread
,是因为pthread
是 POSIX 线程库,不是 C++ 标准库的一部分,需要显式链接 除了pthread
库之外,Linux 系统还提供了许多其他原生库,用于实现各种功能和操作。一些常见的 Linux 原生库包括:
libc
:C 标准库,提供了基本的 C 语言函数和数据结构。libm
:数学库,提供了数学函数和常量。libpthread
:POSIX 线程库,用于多线程编程。librt
:实时库,提供了一些实时特性的函数。libdl
:动态链接库,用于动态加载和链接共享库。libcrypto
和 libssl
:加密库,提供了加密算法和 SSL/TLS 支持。这些原生库都是 Linux 系统提供的标准库,可以在开发 Linux 应用程序时直接使用
pthread_create
函数用于在 POSIX 线程(pthread)中创建一个新的线程。以下是 pthread_create
函数的详细讲解:
函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数说明
thread
:输出型参数,指向新创建线程的标识符的指针(返回新线程id,方便我们管理)。在成功创建线程后,该指针将包含新线程的标识符。
pthread_t其实就是一个无符号长整型
attr
:用于指定线程的属性。如果为 nullptr
,则使用默认属性(一般都是用默认)。
start_routine
:指向线程函数的指针,即新线程将执行的函数。
arg
:传递给线程函数的参数。其实就是调用start_ routine(arg);调用这个函数
返回值
注意事项
void*
类型的参数,并返回 void*
类型的结果。#include <pthread.h>
。pthread
库,例如使用 -lpthread
选项。pthread_self()
函数是 POSIX 线程库中的一个函数,用于获取当前线程的线程 ID(Thread ID)。它返回一个 pthread_t
类型的值
函数原型:
pthread_t pthread_self(void);
pthread_self()
函数没有参数,直接返回当前线程的线程 ID。
返回值:
pthread_self()
函数返回一个 pthread_t
类型的值,该值是当前线程的线程 ID
线程 ID 的用途: 线程 ID 是用来唯一标识一个线程的值。在多线程编程中,每个线程都有自己的线程 ID,可以用来区分不同的线程。线程 ID 在创建线程、管理线程、线程同步等操作中都有重要的作用。
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
using namespace std;
void *test(void *arg)
{
while (true)
{
std::cout << "I'm new thread, pid: " << " new thread id pthread_self:" << pthread_self() << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, test, nullptr);
while (true)
{
std::cout << "I'm main thread, pid: " << getpid() << " new thread id(tid): " << tid << " "
<< " main thread id: " << pthread_self();
sleep(1);
}
return 0;
}
在多线程编程中,无法确定哪个线程会先运行,因为线程的执行顺序由操作系统的调度器(scheduler)决定
pthread_join()
函数是 POSIX 线程库中的一个函数,用于等待指定的线程结束执行。具体来说,pthread_join()
函数会阻塞当前线程,直到指定的线程结束执行。一般情况下,主线程可以使用 pthread_join()
函数来等待其他线程的结束,以确保在主线程继续执行之前,其他线程已经完成了它们的任务。
int pthread_join(pthread_t thread, void **retval);
参数:
thread
:要等待的线程的标识符(通常是通过 pthread_create()
创建的线程返回的 pthread_t
结构体)。retval
:用于获取被等待线程的返回值(我们用来获取一些信息)。返回值:
线程退出只有三种情况:
现在,我们已经能通过进程等待来获取代码执行结果,来确认是否是前两种情况
我们在一开始便点出一个结论:同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的
所以:多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出,这也是为什么pthread_join()
函数不考虑异常的原因,由其父进程来考虑
那么线程该怎么退出呢?——使用exit()
函数行吗?不行,使用exit
也时进程直接结束了。
线程终止的方式:
pthread_exit()
函数pthread_exit()
函数是 POSIX 线程库中的一个函数,用于终止当前线程的执行并返回一个指定的值。当调用 pthread_exit()
函数时,当前线程会立即终止,不会影响其他线程的执行。
函数原型
void pthread_exit(void *value_ptr);
value_ptr
:指向当前线程的返回值的指针。线程的返回值可以是任意类型的数据,因为 value_ptr
是一个 void
指针。
value_ptr
参数可以传递线程的返回值,其他线程可以通过 pthread_join()
函数获取该返回值。
注意事项
pthread_exit()
函数来终止线程。pthread_exit()
函数会终止整个进程,因为主线程的退出会导致整个进程的退出。pthread_exit()
函数而是自然结束(执行完线程函数),线程的返回值默认为 NULL
。#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <string>
using namespace std;
void *test(void *arg)
{
string name = (char *)arg;
int count = 5;
while (count--) // 5秒后会结束
{
cout << "new thread's name:" << name << endl;
std::cout << "I'm new thread, pid: " << " new thread id pthread_self:" << pthread_self() << endl;
sleep(1);
}
pthread_exit((void *)123);
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, test, (void *)"thread_001");
// 这里是主线程
void *ret_newthread = nullptr;
int n = pthread_join(tid, &ret_newthread);
cout << (long long)ret_newthread << endl; // 这里我们的云服务器是64位,那void* 就是8个字节,所以强转为long long
return 0;
}
pthread_cancel()
函数是 POSIX 线程库中的一个函数,用于向指定线程发送取消请求,请求该线程终止执行
函数原型:
int pthread_cancel(pthread_t thread);
参数:
thread
:要取消的线程的标识符(通常是通过 pthread_create()
创建的线程返回的 pthread_t
tid)。功能:
pthread_cancel()
函数用于向指定线程发送取消请求,请求该线程终止执行。pthread_cancel()
函数并不会立即终止目标线程的执行,而是发送一个取消请求,目标线程可以在适当的时候响应取消请求并终止执行。pthread_setcancelstate()
和 pthread_setcanceltype()
函数来设置。返回值:
pthread_cancel()
函数返回值为0。注意事项:
pthread_cancel()
函数并不保证目标线程会立即终止执行,目标线程需要在适当的时候检查取消请求并做出响应。pthread_testcancel()
函数来显式检查取消请求并做出响应。join
了,只是主线程不需要再等待了,还是要共享资源的。当主线程结束,新线程也结束;新线程出现错误,进程也会结束
pthread_detach
是一个在 POSIX 线程编程(也称为 Pthreads)中常用的函数,用于改变一个线程的属性,使得当该线程终止时,其相关的资源(如线程栈)能够被系统自动回收,而不需要其他线程显式地调用 pthread_join
。
在 Pthreads 中,线程有两种状态:可连接的(joinable)和分离的(detached)。
pthread_join
来回收这些资源。这允许我们访问线程的退出状态或返回值。pthread_join
。pthread_detach
函数的原型如下:
int pthread_detach(pthread_t thread);
thread
:这是你想要改变其状态的线程的线程标识符(ID)。返回值:
如果成功,返回 0;如果失败,返回错误码。
注意事项
pthread_join
来回收它的资源了。如果你尝试这样做,pthread_join
会返回一个错误。pthread_detach
将会立即释放其资源,就像你已经调用了 pthread_join
一样。但是,请注意,你将无法获取线程的退出状态或返回值。pthread_join
来回收该线程的资源,那么将线程设置为分离状态通常是一个好主意,因为它可以避免资源泄漏和相关的管理开销。线程的优点:
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多:
而且线程间切换的上下文也少一点:地址空间、页表都是一份(当然,这不是大头)
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现:
这不意味着线程越多越好,因为线程多了后,线程切换的消耗也会大大增加,一般都是CPU有几核就用几个
线程缺点:
线程异常:
线程独占的:
线程共享:
new
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <cstdlib>
#include <vector>
using namespace std;
class Task
{
public:
Task()
{
}
void SetData(int x, int y)
{
_datax = x;
_datay = y;
}
int Excute()
{
return _datax + _datay;
}
~Task()
{
}
private:
int _datax;
int _datay;
};
class ThreadData : public Task
{
public:
ThreadData(int x, int y, const string &threadname) : _threadname(threadname)
{
_t.SetData(x, y);
}
string GetThreadname()
{
return _threadname;
}
int RunTask()
{
return _t.Excute();
}
private:
string _threadname;
Task _t;
};
class Result
{
public:
Result()
{
}
~Result()
{
}
void GetResult(int result, const std::string &threadname)
{
_result = result;
_threadname = threadname;
}
void PrintResult()
{
std::cout << _threadname << ": " << _result << std::endl;
}
private:
int _result;
string _threadname;
};
void *threadTask(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
string name = td->GetThreadname();
int result = td->RunTask();
Result *res = new Result(); // new出来的方便返回
res->GetResult(result, name);
sleep(2);
delete td;
return res;
}
const int thread_num = 4; // 我们创建四个
int main()
{
vector<pthread_t> threads;
for (int i = 0; i < thread_num; i++)
{
char thread_name[20];
snprintf(thread_name, sizeof(thread_name), "thread-00%d", i + 1); // 新线程名字写好
ThreadData *td = new ThreadData(1, 2, thread_name); // new好一个自定义变量后
pthread_t tid;
pthread_create(&tid, nullptr, threadTask, td); // 我们让线程执行函数接受一个类变量
threads.push_back(tid); // 把4个线程的tid放进vector里面
}
// 到了这里,开始等待
vector<Result *> result;
void *ret = nullptr;
for (auto &e : threads)
{
pthread_join(e, &ret); // 返回结果已经到ret里了
result.push_back((Result *)ret);
} // 到这里就说明已经push完了
// 下面直接开始遍历打印就行
for (auto &res : result)
{
res->PrintResult();
delete res;
}
return 0;
}
#include <iostream>
#include <thread> // C++里的库
#include <unistd.h>
using namespace std;
void threadrun(int num)
{
while (num)
{
cout << "I am a thread, num: " << num << endl;
sleep(1);
}
}
int main()
{
std::thread t1(threadrun, 10);
std::thread t2(threadrun, 10);
std::thread t3(threadrun, 10);
std::thread t4(threadrun, 10);
while (true)
{
std::cout << "I am a main thread " << std::endl;
sleep(1);
}
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
如果我们直接使用
g++ test.cc -std=c++11
但是会发现: undefined reference to `pthread_create’ 这不是我们Linux里的原生库吗
结论:C++11的多线程,是对原生线程的封装
vs
下也是依然能运行,但是我们上次使用的Linux
原生库里的pthread_create
就不能再vs
下面运行
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <vector>
#include <pthread.h> //Linux的原生线程库
namespace MyThread // 模拟c++的线程库,也对Linux原生库进行封装
{
template <typename T>
using func_t = std::function<void(T)>; // 把返回值为空,参数是T的函数对象包装
// 就相当于typedef std::function<void(const T&)> func_t;
template <typename T>
class Thread
{
public:
Thread(func_t<T> func, T data, const std::string &name = "none-name") // 构造函数
: _fun(func), _data(data), _thread_name(name), _stop(true)
{
}
~Thread() {}
void Excute()
{
_fun(_data);
}
static void *runfunction(void *args) // 这个函数是线程执行的函数,我们的目的是运行传过来的函数
{
Thread<T> *self = static_cast<Thread<T> *>(args); // 把void* 强转为该类的类型
self->Excute();
// self->_fun(_data);
return nullptr;
}
bool Start() // 线程的启动函数
{
int n = pthread_create(&_tid, nullptr, runfunction, this);
if (!n)
{
_stop = false; // 说明没有stop
return true;
}
else
{
return false;
}
}
void Detach() // 线程分离(非堵塞等待)
{
if (!_stop)
{
pthread_detach(_tid);
}
}
void Join() // 线程等待
{
if (!_stop) // 当线程没有停止时,才能等
{
pthread_join(_tid, nullptr);
}
}
std::string name()
{
return _thread_name;
}
void Stop()
{
_stop = true;
}
private:
pthread_t _tid;
std::string _thread_name; // 线程的名字
func_t<T> _fun; // 要线程执行的函数
T _data; // 函数使用的数据类型
bool _stop; // 线程是否停止
};
}
#endif // 与前两行形成格式
今天就到这里啦,马上就把写的笔记一个一个发出来了