1、线程的基本概念
线程的基本概念,我们看下定义:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程,但轻量进程更多指内核线程,而把用户线程称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈,寄存器环境,自己的线程本地存储。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
(1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:
(1)线程状态。
(2)当线程不运行时,被保存的现场资源。
(3)一组执行堆栈。
(4)存放每个线程的局部变量主存区。
(5)访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
(2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
(3)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
(4)共享进程资源。
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
用人话说就是:从前一个人从头干到尾的工作,现在拿两个人去做,给同样的设备、工资和条件。但是呢,这个新来的人听老员工的安排。
2、thread的定义&创建
thread源代码定义如下:
class thread
{
public:
class id;
typedef void *native_handle_type;
thread() noexcept;
template<class _Fn,
class... _Args,
class = enable_if_t<!is_same_v<remove_cv_t<remove_reference_t<_Fn>>, thread>>>
explicit thread(_Fn&& _Fx, _Args&&... _Ax)
{ // construct with _Fx(_Ax...)
_Launch(&_Thr,
_STD make_unique<tuple<decay_t<_Fn>, decay_t<_Args>...> >(
_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...));
}
~thread() noexcept;
thread(thread&& _Other) noexcept
: _Thr(_Other._Thr);
thread& operator=(thread&& _Other) noexcept;
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;
void swap(thread& _Other) noexcept;
_NODISCARD bool joinable() const noexcept;
void join();
void detach();
_NODISCARD id get_id() const noexcept;
_NODISCARD static unsigned int hardware_concurrency() noexcept;
_NODISCARD native_handle_type native_handle();
private:
thread& _Move_thread(thread& _Other);
_Thrd_t _Thr;
};
std::thread
default thread() noecept; //默认的构造函数,什么都没有,即创建一个新的空的thread执行对象。
initialization template <class Fn,class ... Args>
explicit thread(Fn&& fn,Args&&... args);//构造模板,设置执行的函数,并且传入多个参数。
copy[deleted] thread(const thread&) = delete;//拷贝构造函数=delete,也就是说 thread 不支持拷贝构造。
move() thread(thread&& x) noexcept;//move操作,即用新的thread代替x的所有功能。
具体的创建线程示例如下:
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void add_1(int n)
{
std::cout << "Enter "<< __function__ << "thread..."<< std::endl;
for(int i = 0;i < 5;i++){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Exit "<< __function__ << "thread..."<< std::endl;
}
void add_2(int ¶m)
{
std::cout << "Enter "<< __function__ << "thread..."<< std::endl;
for(int i = 0;i < 10;i ++) {
++param;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Exit "<< __function__ << "thread..."<< std::endl;
}
int main()
{
int n = 0;
std::thread test_t1; //创建一个新的空的thread执行对象。
std::thread test_t2(add_1,n+1);//创建thread执行对象,并传递参数n+1。
std::thread test_t3(add_2,std::ref(n));//创建thread执行对象,并传递参数n。
std::thread test_t4(std::move(test_t3));//move操作,用test_t4代替test_t3进行线程的操作。
test_t2.join();
test_t4.join();
std::cout<< "N = "<<n<<std::endl;
}
当然了,是可以使用lamb、类函数、function模板的,具体的示例如下:
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
//函数
void func_add(int parama,int paramb,int ¶mc)
{
std::cout << __func__<<std::endl;
paramc = paramc + parama + paramb;
std::cout << __func__ << " paramc = "<< paramc<<std::endl;
}
//类
class Add
{
public:
Add(){}
~Add(){}
void Add_add(int parama,int paramb,int ¶mc)
{
std::cout << __func__<<std::endl;
paramc = paramc + parama + paramb;
std::cout << __func__ << " paramc = "<< paramc<<std::endl;
}
static void static_add(int parama,int paramb,int ¶mc)
{
std::cout << __func__<<std::endl;
paramc = paramc + parama + paramb;
std::cout << __func__ << " paramc = "<< paramc <<std::endl;
}
};
//lambd函数对象
auto lamb_add = [](int parama,int paramb,int ¶mc) {std::cout << __func__<<std::endl; paramc = paramc + parama + paramb; std::cout << __func__ << " paramc = "<< paramc<<std::endl;};
//function
std::function <void(int, int,int &)> func_test = func_add;
int main()
{
int a = 10;
int b = 20;
int c = 0;
Add test_add;
std::thread thread1(func_add, a, b, std::ref(c));
if (thread1.joinable())
thread1.join();
std::cout << "函数func_add c = "<< c << std::endl;
std::thread thread2(Add::static_add, a, b, std::ref(c));
if (thread2.joinable())
thread2.join();
std::cout << "类的static add c = "<< c << std::endl;
std::thread thread3(&Add::Add_add,&test_add, a, b,std::ref(c));
if (thread3.joinable())
thread3.join();
std::cout << "类的Add_add c = "<< c << std::endl;
std::thread thread4(lamb_add, a, b, std::ref(c));
if (thread4.joinable())
thread4.join();
std::cout << "lamb_add c = "<< c << std::endl;
std::thread thread5(func_test,a,b,std::ref(c));
if (thread5.joinable())
thread5.join();
std::cout << "function c = "<< c << std::endl;
return 0;
}
//OUT
//func_add
//func_add paramc = 30
//函数func_add c = 30
//static_add
//static_add paramc = 60
//类的static add c = 60
//Add_add
//Add_add paramc = 90
//类的Add_add c = 90
//operator()
//operator() paramc = 120
//lamb_add c = 120
//func_add
//func_add paramc = 150
//function c = 150
3、join、joinable、detach函数介绍
thread::join()的功能是,阻塞当前调用的线程,知道其标识的线程结束。在join()函数执行期间会清理子线程的内存和资源空间。这个时候主线程会一直阻塞在调用的地方。直到join()函数返回,
thread::joinable()函数用于判断主线程和子线程的连接状态,即返回true时,主线程和子线程是存在关联关系的,返回false时,主线程和子线程不存在关联关系。
thread::detach()函数是分离执行的线程。允许子线程独立运行,即子线程可以脱离主线程运行,但是其运行结束后子线程会自动释放自己所占有的系统资源,不需要主线程主动释放。非常的银杏。但是存在一种情况,就是主线程退出后,会一同把其创建的子线程也销毁掉。这点是需要注意的。
具体的使用方法如下(主要是关注打印顺序):
/*************************************************************************
> File Name: thread_exit.cpp
> Author: 小和尚敲木鱼
> Mail:
> Created Time: Wed 22 Sep 2021 07:46:50 AM PDT
************************************************************************/
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
/*****************************文件说明***********************************
* 1、线程退出 join() detach()
* 2、joinable()
***********************************************************************/
void handlesomething1()
{
std::cout << "[" << __func__ << "]" << " doing some thing"<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "[" << __func__ << "]" << "done..." << std::endl;
}
void handlesomething2()
{
std::cout << "[" << __func__ << "]" << " doing some thing"<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "[" << __func__ << "]" << "done..." << std::endl;
}
//
/*
int main(int agc,char * agv[])
{
std::cout << "main thread begin"<< std::endl;
std::thread thread1(handlesomething1);
std::thread thread2(handlesomething2);
thread1.join();
thread2.join();
std::cout << "main thread waiting... exit" << std::endl;
return 0;
}*/
//OUT
//main thread begin
//[handlesomething2] doing some thing
//[handlesomething1] doing some thing
//[handlesomething2]done...
//[handlesomething1]done...
//main thread waiting... exit
//
//
/*
int main(int agc,char * agv[])
{
std::cout << "main thread begin"<< std::endl;
std::thread thread1(handlesomething1);
std::thread thread2(handlesomething2);
thread1.detach();
thread2.detach();
std::cout << "main thread waiting... exit" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}*/
//OUT
//main thread begin
//[handlesomething1] doing some thing
//main thread waiting... exit
//[handlesomething2] doing some thing
//[handlesomething1]done...
//[handlesomething2]done...
//
4、operrator=介绍
线程时不可直接复制的,但是支持移动。
//转移
thread& operator=(thread&& _Other) noexcept;
thread(const thread&) = delete;
//拷贝
thread& operator=(const thread&) = delete;
通过上面的定义,可以看到other是一个右值的话,则会进行资源的所有权转移。但是如果other不是一个右值,则被禁止。
5、pthread exit
5.1 return
线程函数的return返回。return返回这种方式是最安全的,在返回后,会调用_endthreadex()清理资源(_beginthreadex申请的资源)。同时也要保证申请的对象内存释放。
5.2 _endthreadex() & ExitThread()
使用这两个API退出线程,则不会执行线程的return返回,不能保证线程内申请的类对象完全释放,容易造成内存泄漏。
5.3 TerminateThread()
TerminateThread原型如下:
BOOL TerminateThread(
HANDLE hThread,
DWORD dwExitCode);
TerminateThread能够撤消任何线程。hThread参数用于标识被终止运行的线程的句柄。这是windows下停止线程的API函数。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。注意TerminateThread函数是异步运行的函数,其是通知系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
5.4 进程退出
进程退出适用于,当前进程只有一个线程,可以采用进程退出的方式直接退出线程。
进程退出:
1.return返回退出
在main函数中执行return可以终止进程,并将控制权交给调用函数。一般原则程序执行正常退出return 0,而执行函数出错退出return -1。
2.使用exit退出
exit()是一个标准C库函数,执行exit()会进行一些清理工作,最后调用_exit()函数。
函数原型:
void exit(int status);
3.调用_exit()和_Exit()
5.5 pthread_cancel
pthread_cancel的头文件:
<pthread.h>
int pthread_cancel(pthread_t thread);
主线程持有子线程的线程ID,通过函数pthread_cancel发送cancle信号,成功返回0,否则失败描述符。对于没有目标线程也会导致信号发失败(返回信号 ESRCH)。pthread_cancel只是向目标线程发送cancle信号,至于目标线程如何处理并且怎么结束是其自己控制的。注意的是,发送了取消信号之后,不会立即取消,得等到子线程下次调起了之后再自己执行退出。
6、总结
线程的创建和控制还是比较简单,主要是注意点是线程创建时的参数传入时,引用需要使用std::ref()函数。其余的线程相关函数后续补充。