C++11标准(4)——并发库(多线程)

欢迎来到博主的专栏:c++杂谈
博主ID:代码小豪


c++11新增了与并发相关的库,包含线程、以及互斥、同步等与线程安全相关的库,与linux中所使用POSIX库不同,并发库是将其进行了封装,不再是面向过程的使用方式,并且添加了一些c++11的特性,比如右值引用,可变参数模板等。

那么这么做有什么好处呢?第一使用并发库可以跨平台,比如在linux环境下,并发管理使用的pthread相关的函数,在windows下则是Thread。而c++并发库底层是对各个系统的线程库进行封装,所以不同的环境使用并发库的方法是一致的。

下面是linux和windows的线程创建的差异对比

//LINUX pthread库创建线程
 int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, void *
 (*start_rtn)(void*), void *arg);
 
 // windows线程创建API 
HANDLE CreateThread(
 LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
 SIZE_T dwStackSize,//initialstacksize
 LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
 LPVOID lpParameter,//threadargument
 DWORD dwCreationFlags,//creationoption
 LPDWORD lpThreadId//threadidentifier
 )

而关于线程的创建、调度、回收等相关原理,博主在linux开发的专栏详细说明过,因此不再赘述,这里放出相关的链接
线程操作
互斥同步

c++11中与线程相关的库叫做<thread>。而其中线程被封装的类是thread::thread。

thread的相关函数

default (1)	
thread() noexcept;
initialization (2)	
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
copy [deleted] (3)	
thread (const thread&) = delete;
move (4)	
thread (thread&& x) noexcept;

一般情况下使用构造函数2更多,因为其支持可变参数,使用起来更加的灵活,thread::thread不存在拷贝构造函数和赋值重载函数,所以无法将一个thread对象拷贝给另一个thread对象。

这里我们拿构造函数2为例,fn表示未来线程执行的任务,可以是一个函数指针、函数对象、lambda表达式。Args表示执行该任务所需要的参数。

void thread1run(int a, int b) {
	std::cout <<"thread1 do:"<< a + b << std::endl;
}

void thread2run(std::string str) {
	std::cout <<"thread2 do:" << str << std::endl;
}

int main() {
	std::thread t1(thread1run,30,50);
	std::thread t2(thread2run,"hello, world");
	std::thread t3([](std::string str)->void {
		std::cout << "thread3 do:" << str << std::endl;
		},"hello,world");

	t1.join();
	t2.join();
	t3.join();
	return 0;
}
void join();

thread对象一旦创建,就会立即运行,当一个线程执行结束时,需要进行回收,因为线程本质上一种资源,会占用内存当中的空间,如果不进行回收,就会导致内存泄漏。而回收所使用的成员函数为join,比如线程对象t1执行结束,主线程调用t1.join(),就会将t1进行回收,不过前提是t1的任务已经执行完毕,否则主线程将会阻塞等待在t1.join()中,直到t1结束。

既然说一个线程如果执行结束,并不会主动的退出,需要主线程使用join去回收,那么如果我们希望一个线程,他不需要主线程去进行回收,也就是不用去调用join,那么该怎么办呢?答案是使用detach。使用detach后的线程将会是独立的,这意味不需要主线程去join来回收它,当其结束执行后,将会自动退出,不会占用资源。

bool joinable() const noexcept;

如果一个线程对象,需要join来回收,那么这个线程就叫做joinable;如果一个线程对象,不需要join来回收,那么这个线程则是non joinable。成员函数joinable就是用来检测该线程是否是joinable的。若为真,返回true,若为假,则返回false。

那么什么样的线程对象是non joinable的呢?

  • 1、以默认构造函数创建的线程对象。
  • 2、使用detach的独立线程。
  • 3、被join回收后的线程
  • 4、因被move构造而移除的线程
void thread2run(std::string str) {
	std::cout <<"thread2 do:" << str << std::endl;
}

void testjoinable(std::thread& t,std::string t_name) {
	if (t.joinable()) 
		std::cout << t_name << " is joinable"<<std::endl;
	else 
		std::cout << t_name << " is non-joinable"<<std::endl;
}

int main() {
	std::thread t1;												//default_constructor
	std::thread t2(thread2run,"hello, world");
	std::thread t3([](std::string str)->void {
		std::cout << "thread3 do:" << str << std::endl;
		},"hello,world");

	t3.detach();

	testjoinable(t1, "thread1");//thread1 is non-joinable
	testjoinable(t2, "thread2");//thread2 is joinable
	testjoinable(t3, "thread3");//thread3 is non-joinable
	//t1.join();
	t2.join();
	//t3.join();

	testjoinable(t2, "thread2");//thread2 is non-joinable

	return 0;
}

要注意,对non_joinable的线程使用join是会导致崩溃的,因此,我们可以在使用join之前,判断一下该线程是否是joinable。

	if(t1.joinable()) t1.join();
	if(t2.joinable()) t2.join();
	if(t3.joinable()) t3.join();

thisthread

有没有考虑到一点,线程对象只有在主线程中才能访问到,那么如果我们想要在线程中对该线程进行一些控制,那么该怎么做呢?答案是线程中可以使用namespace thisthread,里面包含了四种函数,注意thisthread中的一切函数,都是只对本线程生效。

thread::id get_id() noexcept;
//获取当前线程的ID
std::thread::id main_thread_id = std::this_thread::get_id(); //主线程 ID

void is_main_thread() {  //判断是否是主线程的ID
	std::cout<<std::this_thread::get_id()<<":" ;
//其他线程使用thisthread::getid,只会获取该线程的ID
    if (main_thread_id == std::this_thread::get_id())   
        std::cout << "This is the main thread.\n";
    else
        std::cout << "This is not the main thread.\n";
}

int main()
{
    is_main_thread();
    std::thread th(is_main_thread);
    th.join();
}

线程ID的虽然是一个整数类型,但是在C++中并没有直接用int等整数类型作为线程ID的类型,而是thread::id类型,这是因为不同的平台下对于线程ID有不同的使用标准,所以C++选择统一使用一个类来封装,第二个点就是,线程ID支持在unorder_map等容器中,作为key值索引。

void yield() noexcept;

yield是主动让出当前线程的执⾏权,让其他线程先执行。此函数的确切行为依赖于实现,特别是取
决于使⽤中的OS调度器机制和系统状态。例如,先进先出实时调度器(Linux的SCHED_FIFO)
会挂起当前线程并将它放到准备运⾏的同优先级线程的队列尾。比如下面的案例代码:

std::atomic<bool> ready(false);

void count1m(int id) {
    while (!ready) {             // 等待主进程将ready置为true
        std::this_thread::yield();	//如果ready没有为true,那么线程就不会开始计算
        //因为一直调用yield,切换下一个线程执行,直到ready为true。
    }
    for (volatile int i = 0; i < 1000000; ++i) {}
    std::cout << id;
}

int main()
{
    std::thread threads[10];
    std::cout << "10个进程比赛谁先算完100万:\n";
    for (int i = 0; i < 10; ++i) threads[i] = std::thread(count1m, i);
    ready = true;               // go!
    for (auto& th : threads) th.join();
    std::cout << '\n';

    return 0;
}

最后的两个函数与时间有关。分别是sleep_forsleep_until

template <class Rep, class Period>
  void sleep_for (const chrono::duration<Rep,Period>& rel_time);

template <class Clock, class Duration>
  void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);

其中参数chrono::duration和chrono::time_point都是<chrono>库中的类,<chrono>库也是c++11新增的库,该库与时间相关,也许未来会给大家带来chrono库的介绍。总之,我们可以把time_point视为一个时间点,duration视为一个时间段。这两者有什么区别呢?时间段表示一个范围的时间,而时间点表示一个具体的时间刻度,比如五分钟后,表示一个时间段,而9:30,表示一个时间点。

sleepfor可以让线程休眠一个时间段。

void sleep10s() {
    for (int i = 10; i > 0; --i) {
        std::cout << i << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Lift off!\n";
}

int main()
{
    std::thread t1(count1m);

    t1.join();
    return 0;
}

而sleep_until可以让一个线程休眠到一个具体的时间点。比如下面的示例:

#include <chrono>         // std::systemclock
#include <iomanip>        // std::puttime

void SleepNextMin() {
    using std::chrono::system_clock;
    std::time_t tt = system_clock::to_time_t(system_clock::now());//获取当前时间

    struct std::tm* ptm = std::localtime(&tt);
    std::cout << "Current time: " << std::put_time(ptm, "%X") << '\n';

    std::cout << "Waiting for the next minute to begin...\n";
    ++ptm->tm_min; ptm->tm_sec = 0;								//将时间点设置为下一分钟0刻
    std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));

    std::cout << std::put_time(ptm, "%X") << " reached!\n";
}

int main()
{
    std::thread t1(SleepNextMin);

    t1.join();
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码小豪

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值