C++并发编程之Thread创建和退出

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 &param)
{
    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 &paramc)
{
	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 &paramc)
	{
		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 &paramc)
	{
		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 &paramc) {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()函数。其余的线程相关函数后续补充。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: 《并发编程实战》是一本经典的并发编程书籍,其中包含了丰富的代码示例。这本书的代码示例非常有实战意义,可以帮助开发者在处理并发编程中的各种问题时提供参考。其中的代码示例主要涉及线程池、CAS、原子操作、锁、并发容器、BlockingQueue、CyclicBarrier和Semaphore等相关知识点。 本书的代码示例分布在各个章节中,开发者可以根据需要选择不同的示例进行学习和实践。例如,在线程池相关章节,作者提供了诸如ThreadPoolExecutor、ExecutorCompletionService等类的实现,并且提供了基于可扩展的ThreadPoolExecutor来实现动态调节线程池大小的代码示例。这些示例可以帮助开发者深入了解线程池的实现方式,以及如何进行线程池的调优。 在锁相关章节,作者提供了诸如ReentrantLock和读写锁ReentrantReadWriteLock等类的实现,并且提供了一些实际应用场景下的代码示例,例如票务系统和登录系统。这些示例可以帮助开发者了解锁的原理及其使用方法。 本书同时也介绍了一些常用的并发容器,例如ConcurrentHashMap、ConcurrentLinkedQueue等,在使用这些容器时需要注意线程安全的问题。作者为这些容器提供了详细的使用方法和代码示例,帮助开发者了解如何高效地使用这些容器。总之,《并发编程实战》的代码示例非常有价值,具有一定参考和借鉴意义,可以帮助开发者更好地掌握并发编程知识。 ### 回答2: 《Java并发编程实战》一书的源码是该书的大部分内容的实现代码。这些代码的使用可以帮助读者更好地理解并发编程的实现方式,同时也可以成为读者自己学习并发编程的参考资料。 该书的源码包括一些经典的并发编程实现,例如线程池、锁、原子变量、阻塞队列等。这些实现具有实用性和普遍性,可以帮助读者在自己的开发中解决并发编程问题。同时,该书的源码还包括一些基于实际场景的例子,让读者可以更好地理解并发编程在实际项目开发中的应用。 在使用该书源码时,读者需要关注一些细节问题,例如多线程环境下的原子性、可见性和有序性等。同时,读者还需要学会如何调试和排查多线程程序的问题,以保证程序的正确性和稳定性。 总之,该书的源码是学习并发编程的重要工具之一,读者需要认真学习源码并结合实际项目开发进行练习。只有这样,才能真正掌握并发编程的技巧和应用。 ### 回答3: 《Java并发编程实战》是一本著名的并发编程领域的经典著作,其中的源代码涵盖了Java并发编程的多个方面,非常有学习和参考的价值。 该书中的源代码主要包括了多线程并发、线程池、ThreadLocal、锁、信号量、条件等一系列并发编程相关的实例和案例,涵盖了从最基础的并发操作到应用场景的实践。 通过学习并实践这些源代码,我们可以更好地理解并发编程的思路和原理,掌握并发编程的技能和方法,提高代码质量和性能。同时,还可以培养我们的编码思维和能力,为我们今后的编程工作和研究打下坚实的基础。 总之,《Java并发编程实战》的源代码是具有非常实用和价值的,并发编程相关领域学习者和从业者都可以将其作为一个良好的学习和实践资源,不断探索和尝试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给大佬递杯卡布奇诺

你们的鼓励就是我传作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值