C++并发编程(二)线程管控

【C++】并发编程 一、线程创建与管理 1.1 并发 1.1.1 并发与并行 并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。 1.1.2 硬件并发与任务切换 单片机上的单核处理器支持并发多任务处理,依靠任务切换实现,与多核处理器上的多任务并发处理方式不同。 双核处理器并行执行(硬件并发)对比单核处理器并发执行(任务上下文切换) 双核处理器均并发执行(一般任务数远大于处理器核心数,多核并发更常见) 1.1.3 多线程并发与多进程并发 任务 阅读详情

每个C++程序必须至少有一个线程(main()的线程),由C++运行时库(C++ runtime)启动,随后可以发动多线程,它们以别的函数作为入口(entry point),当mian返回时,程序会退出,同理,线程中的入口函数返回时,线程也随之终结。

目录

1.线程的发起

2.线程分离

2.1后台运行线程

3.等待线程完成

3.1抛出异常 

3.2线程安全汇合

4.向线程函数传递参数

4.1参数的隐式转换

4.2传入非const引用 

4.3传递类的成员函数

4.4动态对象归属权的转移

5.线程归属权的转移 

5.1实例间的线程归属权转移

5.2线程在函数间转移

6.使用容器装载线程

7.运行时决定线程数量

8.线程识别

9.总结


1.线程的发起

线程通过构建std::thread对象启动,对象指明线程要运行的任务。任何可调用类型(callable type)都适用于std::thread。因此,可以以重载函数对象操作符的类对象作为参数。

        函数调用操作符是(),因此,此操作符的函数重载是operator()()。重载函数调用操作符的类对象称为函数对象或仿函数(functor),因为我们可以像使用函数名一样使用对象名。

        函数对象比函数指针的效率高,因为可以在编译时确定调用的函数,比运行时效率高,另外可以内联展开减少调用开销。

        函数对象的类继承时,若采用非虚函数,子类或子类的引用只会调用父类的方法;采用虚函数时,子类仍调用基类方法,子类引用才会调用子类本身方法。

转自:C++ 函数调用操作符 () 、 函数对象

#include<iostream>
#include<thread>

class A
{
public:
	void operator()()const
	{
		std::cout << "A" << std::endl;
	}
};

int main()
{
	A a;
	std::thread t(a);
	t.join();
}

上述线程对象t,提供函数对象f作为参数,它被复制到属于新线程的存储空间中,并在那里被调用,由新线程执行。

但是要注意解释性问题,若传入的是匿名变量,如“std::thread t(A())”,本意是发动新线程,但会被解释为函数声明,函数名是t,返回std::thread对象,接收的参数是函数指针,函数没有参数传入,返回A对象,解决办法是多一对括号,或列表初始化或lambda表达式:

std::thread t( (A()) );
std::thread t{ A() };
std::thread t([]{ std::cout<<"A"<<std::endl; });

线程启动后,需要明确是要主线程等待其结束,还是让它独立运行。假如等到std::thread销毁之际仍未设置,析构函数会调用std::terminate()终止整个程序:

我们要在对象销毁前做出决定(注意,线程本身可能在汇合或分离前已经结束),如果选择detach,分离时线程还未结束运行,那它将继续运行,只有最终从线程函数返回才结束。

2.线程分离

分离操作为会切断线程与std::thread对象的关联,对象不再可汇合(joinable()返回false),对象销毁时也不会调用std::terminate()。

在选择detach的情况下,主线程不会等待子线程,故需要保证子线程访问的外部数据正确、有效。下面这个例子中,函数f以主线程的局部对象state的引用作为参数,子线程调用detach(),表明不等待,在opps()退出后,t可能仍在运行,但t中的do(i)函数需要调用已经销毁的局部对象state,多线程代码容易导致这种错误。

struct func
{
    int& i;
    func(int& i_) : i(i_) {}
    void operator() ()
    {
        for (unsigned j = 0; j < 1000; ++j)
        {
            do_something(i);
        }
    }
};

void oops()
{
    int state = 0;
    func f(state);
    std::thread t(f);
    t.detach();
}

解决办法:令线程函数完全自含(self-contained),即将数据复制到新线程内部,而不共享数据。若可调用对象(可以像函数调用方式的触发调用的对象就是可调用对象)含有指针或引用,则需要谨慎行事。应该尽量避免:在函数中创建线程,线程分离,并让线程访问函数的局部变量引用。

2.1后台运行线程

调用std::thread的detach会使线程在后台运行,无法获得与之关联的std::thread对象,也无法与之直接通信,其归属权和控制权交由C++运行时库(runtime library)处理,线程退出后,与之关联的资源将被正确回收。

UNIX系统中,有一类线程在后台运行且没有对外的用户界面,成为守护线程(daemon process),沿袭这一概念,分离出去的线程也被称为守护线程。它们几乎在程序的整个生命周期都在运行,如:文件系统监控、清除缓存中的无用数据、优化数据结构等。另外,还可以让分离线程执行“启动后即可自主完成的任务”(a fire-and-forget task),还可以通过分离线程确认线程运行完成。

应用举例:文字处理软件,用户可以同时编辑多个文件,当打开新的文件时,创建新的线程运行该文件的编辑窗口,每个文件相互独立,此时可以使用detach线程:

void edit_document(const std::string& file_name )
{
	open_document(file_name);
	while (!finish_editing())
	{
		user_command cmd = get_user_input();
		if (cmd == OPEN_NEW_DOC)
		{
			const std::string name = get_filename_from_user();
			std::thread t(edit_document, name);
			t.detach();
		}
		else
		{
			process_user_input(cmd);
		}
	}
}

3.等待线程完成

若选择等待新线程完成,则调用join()函数即可。对于给定的某个线程,join()仅能调用一次,成员函数bool joinable()用于判断线程是否可汇合。

3.1抛出异常 

如前文所述,std::thread销毁前需要确保已经调用join()或detach()。然而在打算线程汇合时,调用join()的时机需要讲究,因为如果线程启动后有异常抛出,join()尚未执行,则join()将被忽略。我们可以在catch抛出异常前调用join()来避免。

struc func;//与前文一样的函数
void oops()
{
    int state = 0;
    func f(state);
    std::thread t(f);

    try
    {
        ...    
    }
    catch(...)
    {
        t.join();
        throw;
    }

    t.join();
}

3.2线程安全汇合

假如代码必须保证新线程先结束,之后当前线程的函数才返回,关键在于所有的退出路径必须保证这种先后次序,这需要简洁有效的实现方式。我们可以设计一个类,在其析构函数中调用join(),在创建了新线程后构建该类的实例:

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_) : t(t_) {}
    ~thread_guard() 
    {
        if (t.joinable()) t.join();
    }
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator= (thread_guard const&) = delete;
};

struct func
{
    int& i;
    func(int& i_) : i(i_) {}
    void operator() ()
    {
        for (unsigned j = 0; j < 1000; ++j)
        {
            do_something(i);
        }
    }
};

void oops()
{
    int state = 0;
    func f(state);
    std::thread t(f);
    thread_guard g(t);
    ...
}

thread_guard的析构函数会先调用joinable()判断新线程能否汇合。复制构造函数和复制赋值函数以“=delete”标记,均限定不自动生成,因为产生的新对象生存期可能更长,甚至超过与之关联的线程,导致其它问题。

当oops()执行到末尾时,全体局部对象都将被销毁,按照逆向构建的顺序,首先调用thread_guard的实例g的析构函数,使新线程得以调用join()。即便后面的“...”抛出异常导致opps()退出,依然能保证线程汇合。

4.向线程函数传递参数

4.1参数的隐式转换

void f(int i, const std::string& s);
std::thread t(f, 3, "hello");

f()第二个参数为std::string,字符指针仍以const char*形式传入,进入新线程上下文环境后,才转换为std::string。这一细节值得注意,以下例子会有隐患:

void f(int i, const std::string& s);
void oops(int param)
{
	char buffer[1024];
	sprintf(buffer, "%i", param);
	std::thread t(f, 3, buffer);
	t.detach();
}

sprintf 使用使用一些以“%”开头的格式说明符(format specifications)来指定串的格式,在后边的变参列表中提供相应的变量,函数会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串,该字符串会被打印到第一个参数对应的字符串中。

向线程t中传入的参数有指针buffer,指向char局部数组,我们希望buffer先隐式转换为std::string对象,再将其作为函数参数,然而该操作在线程内才会完成,在此操作完成前oops()函数可能已经退出,局部数组buffer将被销毁,进而引发未定义行为。解决办法是先显式转换,,避免悬空指针:

悬空指针(Dangling Pointer):一个指针的指向对象已被删除,那么就成了悬空指针。

野指针:针指向的位置是不可知的、随机的、无明确限制的。(未初始化)

void f(int i, const std::string& s);
void oops(int param)
{
	char buffer[1024];
	sprintf(buffer, "%i", param);
	std::thread t(f, 3, std::string(buffer));
	t.detach();
}

4.2传入非const引用 

如果线程中函数期望接收的参数是非const引用,std::thread会忽视参数要求,直接复制提供的值,当成move-only型别(只能移动,不能复制)以右值形式传递。函数会收到右值引用,与期望的非const引用不符,遂编译失败:

右值rvalue,本质是固定不变的“值”,不接受赋值,只出现在表达式等号右边。特点是没有标识符,无法提取地址,通常是临时变量和匿名变量,满足移动语义。 

void update(int id, m_data& data);
void oops(int id)
{
	m_data data;
	std::thread t(update, id, data);
	t.join();
}

解决办法是以std::ref()函数加以包装:

void update(int id, m_data& data);
void oops(int id)
{
	m_data data;
	std::thread t(update, id, std::ref(data));
	t.join();
}

4.3传递类的成员函数

若要将某个类的成员函数设定为线程函数,则应该传入该函数的指针,以及合适的对象指针:

class A
{
public:
	void work();
};
A my_a;
std::thread t(&A::work, &my_a);

如果要为成员函数提供参数,可以在后边继续添加参数,类似于编译器的绑定机制。

在调用类的非静态成员函数时,编译器会隐式添加所操作对象的地址作为第一个参数,用于绑定对象和成员函数。例如:类A具有成员函数func(int x),而a是类A的对象,则调用func(2)等价于调用A::func(&a, 2)。具体参考《深度探索C++对象模型》

4.4动态对象归属权的转移

我们知道,对于给定对象,只能存在唯一一个std::unique_ptr指向它,若该实例被销毁,所指对象便随之删除。通过移动构造函数或移动赋值操作符可以使对象的归属权在多个std::unique_ptr间转移,移动后的源对象的值变成NULL指针。作为临时变量的对象会自动发生移动,具名变量则必须通过调用std::move()请求转移:

void process_obj(std::unique_ptr<obj>);
std::unique_ptr<obj> p(new obj);
std::thread t(process_obj, std::move(p));

std::move(p)本身只是将p转换成右值,创建线程时发生一次转移,进入线程内部存储空间(线程库内部进行复制),process_obj()开始执行时,再次转移(函数调用复制参数)。

5.线程归属权的转移 

std::thread类的实例可以转移(movable)但不能复制(not copyable),线程的归属权可以在实例间转移,确保了对于每个线程,任何时候只有唯一的std::thread实例与之关联。

5.1实例间的线程归属权转移

以下是线程归属权的例子,注释有每个实例当前绑定的函数对应的线程,以及相关说明:

void func1();
void func2();

std::thread t1(func1);			//t1:func1
std::thread t2 = std::move(t1);	//t1:NULL; t2:func1
t1 = std::thread(func2);		//t1:func2;t2:func1 隐式执行临时变量的移动操作
std::thread t3;					//t1:func2;t2:func;t3:NULL
t3 = std::move(t2);				//t1:func2;t2:NULL;t3:func1
t1 = std::move(t3);				//t1已关联fun2,std::terminate()终止程序

 注意对象管控着一个线程时,不能简单向它赋值,否则线程会被遗弃。

5.2线程在函数间转移

std::thread支持移动操作,所以函数可以便捷地向外部转移线程的归属权(函数返回std::thread对象):

std::thread f()
{
	void func(int);
	return std::thread(func, 1);
}

同样,归属权也可以转入到函数内部(函数参数为std::thread对象),转入时需要注意具名对象和匿名对象的区别:

void f(std::thread t);
void h()
{
	void func();
	//匿名对象自动调用std::move()
	f(std::thread(func));
	//具名对象
	std::thread t(func);
	f(std::move(t));
}

在线程支持移动语义的情况下,我们可以把线程的归属权转移给thread_guard,其它对象就无法执行该线程的汇合和分离操作了。

前文我们使用thread_guard类在一定程度上保证了线程退出前调用join(),但是一旦thread_guard的生命周期超过管控的线程,还是容易引发各种错误。基于移动语义,我们可以设计scop_thread类,用于确保对象在离开所在作用域前,其线程已经完成:

class scope_thread
{
	std::thread t;
public:
	explicit scope_thread(std::thread t_) : t(std::move(t_))
	{
		if (!t.joinable())
			throw std::logic_error("No thread!");
	}
	~scope_thread()
	{
		t.join();
	}
	scope_thread(const scope_thread&) = delete;
	scope_thread& operator=(const scope_thread&) = delete;
};
struct func
{
    int& i;
    func(int& i_) : i(i_) {}
    void operator() ()
    {
        for (unsigned j = 0; j < 1000; ++j)
        {
            do_something(i);
        }
    }
};
void f()
{
	int state;
	scope_thread t{ std::thread(func(state)) };//解决匿名变量的解释问题
}

这里向scope_thread的构造函数传入新线程,此处state传入的是func而不是std::thread,所以不需要考虑非const参数,与前文的guard_theard不同,此类在构造函数中判断线程能否汇合。初始线程运行至f()末尾,对象t将被销毁,调用析构函数中的join()。在C++20中引入了std::jthread,只要执行析构函数,线程便能自动汇合。

6.使用容器装载线程

基于std::thread的移动语义特性,只要容器同样支持移动意图(move-aware),就可以装载std::thread对象,因此我们可以用vector容器装载多个std::thread对象,这种方法可以用于切分某算法的运算任务:

移动意图(move-aware)指容器能够将元素复制或移动到其内部,可以正确处理只移动对象,且能在容器内部进行移动操作。

void func(int i);
void f()
{
	std::vector<std::thread> threads;
	for (int i = 0; i < 20; ++i)
	{
		threads.emplace_back(func, i);
	}
	for (auto& entry : threads)
	{
		entry.join();
	}
}

上述代码中,使用emplace_back方法直接在vector末尾创建std::thread对象,每个线程上的任务都是自含的,且操作共享数据的结果只由副作用产生。假如f()需要返回值,且返回值依赖于所有线程的结果,那么只有等所有线程执行完毕后,才能核查共享数据,以推算出返回值。

自含(self-contained)指线程内数据齐全,可独立完成子任务而不依赖外部数据。

副作用,C++标准中规定的特定术语,意思是执行状态的改变,一般是计算表达式时对存储在变量中的值进行的修改,此处线程中函数的结果仅依赖于i的值的改变。

7.运行时决定线程数量

我们可以借用C++标准库的std::thread::hardware_concurrency()函数,它返回一个指标,表示程序在各次运行中可真正并发的线程数量,若信息无法获取,则返回0,它能帮助我们完成多线程对完整任务的分解。

以下代码将工作分派给各个线程,并设置一个限定量,每个线程所负责的元素数量都不能低于限定量,从而防止线程过多(线程过饱和oversubscription)造成额外开销。假设没有任何操作抛出异常,后续章节将介绍可直接调用的库函数,此处给出大致思路:

template<typename Iterator, typename T>
struct accumulate_block
{
	void operator()(Iterator first, Iterator last, T& result)
	{
		result = std::accumulate(first, last, result);//(累加起始位置,累加终止位置,累加初始值)
	}
};
template<typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init)
{
	unsigned long const length = std::distance(first, last);//元素总长度[first, last)
	if (!length)//表明没有元素,返回起始元素
	{
		return Init;
	}
	unsigned long const min_per_thread = 25;//每个线程最少处理的元素数
	unsigned long const max_threads = (length + min_per_thread - 1) / min_per_thread;//最大线程数的计算方式,保证了至少有一个线程。
	unsigned long const hardware_threads = std::thread::hardware_concurrency();//获取实际线程数
	unsigned long const num_threads = std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);//实际的线程数在2-max_threads之间。
	unsigned long const block_size = length / num_threads;//按照各线程需要分担的元素数量,将数据切分为多块
	std::vector<T> results(num_threads);//用于存放中间结果

	std::vector<std::thread> threads(num_threads - 1);//用于装载线程,-1因为主线程也算一个线程
	Iterator block_start = first;//初始化block_start迭代器
	for (unsigned long i = 0; i < (num_threads - 1); ++i)//循环创建子线程
	{
		Iterator block_end = block_start;//初始化block_end迭代器
		std::advance(block_end, block_size);//block_end迭代器移动至小块末尾
		threads[i] = std::thread(//启动新线程
			accumulate_block<Iterator, T>(),//调用函数计算小块累加结果
			block_start,
			block_end,
			std::ref(results[i])
		);
		block_start = block_end;//因为STL容器左闭右开特性,下一小块的起始位置为本小块的末端
	}

	accumulate_block<Iterator, T>()(//主线程处理最后一个小块,直接处理到last解决余数问题
		block_start, last, results[num_threads - 1]);
	//等待所有线程完成
	for (auto& entry : threads) 
	{
		entry.join();
	}

	return std::accumulate(results.begin(), results.end(), init);//最后返回累加值
}

本例没有考虑部分类型T(如double和float)的精度损失问题;本例中的迭代器必须是前向迭代器;且为了创建vector<T> results存储中间结果,T必须支持默认构造(default-constructible)。由于无法从线程直接返回值,必须传入results[i]引用,后续章节会介绍future方式从线程返回结果。

上例中我们把变量i赋值给线程ID,若某函数需要获取线程ID就不太方便,C++标准库为每个线程内建了唯一的ID。

8.线程识别

线程ID的型别是std::thread::id,可以在与线程关联的std::thread对象上调用成员函数get_id()获得线程ID,若std::thread对象没有关联任何线程,则std::thread::get_id()将会返回默认构造的std::thread::id对象,表示“线程不存在”。

该型别可随意进行复制且具备完整的比较运算符,比较运算符就所有不相等的值确立了全序关系,用于判断线程是否相同,或排序,或参与任何用途的比较运算,更重要的是能作为关联容器(associative container,包括std::set、std::map、std::multiset、std::multimap)的键值。

全序关系(total order)关于集合的数学概念,满足3个性质:反对称、传递和完全。

我们可以用线程ID识别主线程,使主线程和子线程用不同的方式处理任务,只要在发起新线程前保存std::this_thread::get_id()的结果:

std::thread::id main_thread = std::this_thread::get_id();
void func()
{
	if (std::this_thread::get_id() == main_thread)
	{
		do_main_work();
	}
	do_common_work();
}

我们可以采用关联容器,以线程ID为键值存储每个线程的相关信息,从而关联特定的数据或控制特定线程处理数据的方式。还可以打印出ID用于调试和日志:

std::cout<<std::this_thread::get_id();

9.总结

基础

std::thread对象创建新线程,创建时可用{}解决解释性问题,新线程需要调用join()或detach(),joinable()成员函数判断是否可汇合join()\分离detach(),不能重复选择join和detach。

join()要注意在抛出异常前或线程结束前调用。解决办法:1.抛出异常时catch中调用join();2.创建新类,并在析构函数中调用join(),令复制构造和复制赋值函数=delete,新类在创建新线程后构建。

detach()要注意避免:含有局部指针或引用对象时,局部对象被销毁,而线程仍在运行。

在向线程函数传入参数时

若线程分离,且参数为局部变量的指针/引用,要注意线程内隐式转换导致出现悬空指针,此时改用显示转换。

若函数接收非const引用,要使用std::ref(),按引用传递参数(默认是复制为右值)。

线程处理成员函数时,要额外提供成员对象指针/引用作为第一参数。

转移归属权

函数可以返回std::thread对象向外转移归属权,或接收std::thread对象将归属权转移到内部。(具名对象使用std::move())。

std::thread对象作为构造函数参数可以设计安全类。(保证线程及时汇合/分离)

其它

std::thread::hardware_concurrency()返回线程真正并发数量,以此为依据设计数据块。

std::this_thread::get_id()识别当前线程ID。

C++并发编程 第1章:C++的并发世界 1.1 何谓并发 最简单和最基本的并发,是指两个或更多独立的活动同时发生。 并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时作不同的动作,还有我们每个人都过着相互独立的生活——当我在游泳的时候,你可以看球赛,等等。 1.1.1 计算机系统中的并发 计算机领域的并发指的是在单个系统里同时执行多个独立的任务,而非顺序的进行一些活动。 计算机领域里,并发不是一个新事物:很多年前,一台计算机就能通过多任务操作系统的切换功能,同时运行多个应用程序;高端多处理器服务 阅读详情

相关推荐

c语言实现多线程技术

线程池技术是提前创建多个线程,让这些线程领取任务,执行任务。其实和传统的来一个任务就马上创建一个线程的方法是类似的,因为我们在线程池初始化函数的时候也创建了多个线程,我们都知道创建线程就会去执行线程函数,同样的,我们创建这么多线程的同时也让这些线程去执行了同一个线程函数,只不过我们对这个线程函数进行了处理,在线程函数中:如果任务量为0我们就让这些线程全部阻塞。有任务来了,我们就领任务,怎么领任务呢?

2301_78347044的博客 3886

【C++ 多线程编程|并发编程】

一个程序只有一个进程,该进程拥有至少一个线程。不同进程的地址空间不同,互不相关。同一进程下的不同线程共享同一个地址空间。C++11中引入了thread类和asyn函数来实现多线程。

weixin_41919587的博客 2966

在 Ubuntu 上安装和配置 PostgreSQL 实录

本文介绍了在Ubuntu系统上安装和配置PostgreSQL数据库的完整步骤。主要内容包括:1)通过官方源或PPA源安装最新版本;2)修改配置文件(postgresql.conf和pg_hba.conf)以允许远程访问;3)设置postgres用户密码;4)创建新用户并配置权限;5)防火墙设置和重启服务;6)验证安装和远程连接方法。文章提供了详细的命令行操作指导,帮助用户完成从安装到远程访问的全部配置过程。

qq_58168857的博客 1203

C++并发编程学习(二)—— 线程所有权和管控

首先,在与线程关联的std::thread对象上调用成员函数get_id(),即可得到该线程的ID。如果std::thread对象没有关联任何执行线程,调用get_id()则会返回一个std::thread::id对象,它按默认构造方式生成,表示“线程不存在”​。所以我们可以得出一个结论,就是不要将一个线程的管理权交给一个已经绑定线程的变量,否则会触发线程的terminate函数引发崩溃。步骤6导致崩溃的原因就是将t3管理的线程交给t1,而此时t1正在管理线程运行some_other_function。

AutumnorLiu的博客 362

Jetson Orin NX上CAN总线调试全攻略:从硬件焊接到开机自启动(附常见问题解决方案)

本文详细介绍了在NVIDIA Jetson Orin NX开发板上进行CAN总线调试的全过程,包括硬件焊接、驱动配置、通信测试和开机自启动设置。针对常见问题如信号接反、波特率设置等提供了实用解决方案,帮助开发者快速实现稳定的CAN总线通信。

c2d3e4f的博客 1075

WSL中使用VMD图形化界面

【代码】WSL中使用VMD图形化界面。

handsome213的博客 838

并发编程(2)——线程管控

如果存在一个容器 std::vector<std::thread>,当使用 push_back 将一个线程对象添加到这个容器时,实际上会调用 std::thread 的拷贝构造函数,但是std::thread 将拷贝构造函数delete,编译器会报错。std::vector 中,原始的 std::thread 对象将失去对该线程的所有权(即该对象变为无效),并且只能在 std::vector 中的元素上调用 join() 或 detach() 来管理这个线程的生命周期。当取消被执行时,进行清理和终止线程。

m0_63086198的博客 1221

BUPT软件安全实验三

可以看出栈溢出对程序运行的安全性有很大的威胁,如果不对栈进行保护,只要系统中存在栈溢出漏洞,即可令攻击者通过非正当方法获取操作权限。(6)解决思考题,即:在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过记事本打开shellcode.txt(可使用CreateProcessA或WinExec等API)。查看缓冲区信息,可以看到dest指向地址0x0012FAF0,其为数组的存放起始位置,也就是shellcode注入的起始位置。

7xun's space 1965

[C++ 并发编程] 线程管控

线程管控

A_ns_wer_的博客 1200

2026年全国青少年信息素养大赛算法应用主题赛C++赛项【决赛】模拟卷1:文末附答案

2026年全国青少年信息素养大赛算法应用主题赛C++赛项【决赛】模拟卷1:文末附答案

王老师青少年编程 1176

【AgentScope 2.0】01-不搭 demo,直接解剖一个生产级 Agent 平台:loser-agent 全链路地图

本文介绍生产级Agent平台loser-agent的完整请求旅程,从用户发消息到返回结果共七站:入口鉴权、灰度路由、Agent装配、模型路由、工具执行、持久化与流式响应。课程以真实源码为线索,深入剖析每一步实现逻辑,涵盖灰度发布、权限控制、HITL审批、多模态处理、状态持久化等核心能力。强调框架仅负责Agent运行时语义,平台自建管理请求生命周期中所有非框架部分,适合Java后端开发者系统掌握Agent工程化落地。

loser.with.m的博客 724

C++并发编程实战 #1 线程管控

如果给定一个线程,只要令thread对象与之关联,就能管控该线程的几乎每个细节。每个C++程序都至少有一个线程,即运行main()的线程,它由C++运行时系统启动。随后,可以发起更多线程,以别的函数作为入口。main()函数返回时,程序就会推出;同样,当入口函数返回时,对应线程随之结束。

A11en3的博客 1106

基于Simulink实现电动汽车制动能量回收系统的仿真

通过上述步骤,我们成功实现了基于Simulink的电动汽车制动能量回收系统的仿真研究。该系统能够全面评估制动能量回收系统的性能,验证控制策略的有效性,并通过优化设计提高能量回收效率和制动安全性。引入智能预测:结合人工智能技术,实现更智能的能量回收控制策略。扩展功能:增加对多种车型和制动场景的支持,提升系统通用性。实验验证:将仿真模型应用于实际硬件,进行实验验证,评估其在实际工况下的表现。

xiaoheshang_123的博客 336

C++并发编程之一 初识线程和线程管控_获取有名管道中的结果 单独起一个线程c++

当然也有办法,那就是使用 ‘std::thread::detach()’ 函数使线程对象与其管理的执行线程相分离,将一个线程对象与其执行线程分离后,这个线程对象就不再与其所管理的执行线程相关联,它的执行状态将与执行线程独立。,它包含线程的一些信息,比如线程id,状态,堆栈指针等等。而’std::thread::join()'是一个成员函数,它用于等待该线程对象相关联的线程运行结束,调用 ‘join()’ 函数会阻塞当前线程的运行,直到该线程对象相关联的线程执行完成为止。被分离出去的线程又可被称为。

2401_89191848的博客 1015

中国土地市场网爬取

实习期间老板需要对土地数据进行爬取,想来想去就Google到了.应该算是种类最齐全的土地数据了。Github地址:https://github.com/AnTi-anti/china_land/tree/master。

猫敷雪 5462

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

C++并发编程(1):基本概念、线程管控

主要学习介绍C++并行编程的基础知识,做一些学习记录并分享

工大机器人工作室 1159

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

C++并发编程 -1.线程管理

本栏主要介绍《C++并发实战编程》这本书,。将按照书目录分为 九章节介绍C++并发编程,尽可能简化本书内晦涩难懂知识点。本章节主要讲解线程使用基础。详细介绍线程使用基础(线程发起、等待、参数、异常处理)、线程管控(归属权、并发数量等)。

qq_45604814的博客 1170

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

C++并发编程-2.C++ 线程管控

参考:https://llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Tuk4RfvfBC788LlqnQrWiPiEGW 使用起来比较简单,我们直接构造一个joining_thread对象即可。 5. 选择运行数量 借用C++标准库的std::thread::hardware_concurrency()函数,它的返回值是一个指标,表示程序在各次运行中可真正并发的线程数量.我们可以模拟实现一个并行计算的功能,计算容器内所有

yang_pi_pi的博客 490

C++并发编程实战读书笔记——线程管理

并发的两种途径:多个进程,每个进程只有一个线程(multiple single-threaded processes);每个进程有多个线程(multiple threads in a single process)。多个单线程/进程的启动和通信开销要比单一进程多线程间的启动和通信大。若不考虑共享内存可能带来的问题,多线程是主流语言(包括C++)更青睐的并发途径。此外,C++标准并未对进程间通信提供...

肥叔菌的博客 8815

c++并发编程

众所周不知,掌握多线程开发得计算机天下。接下来,让我们从C++11标准开始学习并发编程。从线程的管控到线程间共享数据,再到并发操作同步,再到……

Lucky_Cpper的博客 1308

C++并发编程学习笔记

C++并发编程学习笔记

qq_46260032的博客 1167

C++并发编程之一 初识线程和线程管控

C++并发编程探索

Awesomewan的博客 430

第二章 线程管控

但是一个很大的隐患是,这个参数的生命周期是在这个函数内部的,当函数结束,这个指针就会悬空,此时线程很有可能还未实现参数类型的转换,也就是说此时线程内部的const char*指针会悬空。如果我们将线程函数的参数设置为引用类型,当我们传递参数时,线程是不知道参数的类型的,当线程拷贝出的参数副本一般是右值的形式,且只能移动不能复制。使用move函数可以将线程的所属权在不同的thread对象之间转移,如果一个thread对象以及关联一个线程,再接收到另一个新线程的所属权时,旧的线程就会被遗弃,线程函数会被终止。

博客 739

《C++并发编程实战》 第2章 线程管控

为什么要用它们而不是直接用 it + n 或 it - it?因为不是所有容器(比如 std::list)都支持 + 或 - 运算符。使用 std::distance 和 std::advance 可以让你的并发算法适用于任何标准容器,增强了代码的通用性。当前线程的ID可以通过调用std::this_thread::get_id()获得。在与线程关联的std::thread对象上调用成员函数get_id()

yang_pi_pi的博客 390

《C++并发编程》《线程管理》

书山有路勤为径,学海无涯苦作舟。

zwh1298454060的博客 1188
上一篇: C++并发编程(一)简介
下一篇: C++并发编程(三)线程间共享数据
Cvincent976
博客等级 码龄6年 34粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值