C++ 多线程学习笔记

std::thread

class thread;:Class to represent individual threads of execution.

void callback1() {
	cout << "hello, callback1" << endl;
}

void callback2(int i) {
	cout << "hello, callback2, i = "<< i << endl;
}

int main() {
	//空参的回调函数
	thread t1(callback1);
	//带有一个参数的回调函数
	thread t2(callback2, 9527);
	t1.join();	//必须回收,否则程序会崩溃
	t2.join();
	return 0;
}

设置线程分离:

void callback() {
	std::cout << "hello,callback" << std::endl;
}
int main() {
	std::thread t1(callback);
	t1.detach();		//设置线程分离
	this_thread::sleep_for(chrono::seconds(1));
	return 0;
}

通过std::thread创建的线程是不可以复制的,但是可以使用std::move()移动

int main() {
	std::thread t1([](){std::cout << "hello, callback" << std::endl;});
	std::thread t2(std::move(t1));

	t2.join();
	return 0;
}

注意在为线程回调传递引用参数时必须用 ref 转化,因为 thread 的构造函数接受的参数是右值引用类型的:

template <class Fn, class... Args>explicit thread (Fn&& fn, Args&&... args);
void foo(int& x) {
    x += 1;
}

int main() {
    int a = 1;
    thread t(foo, ref(a));
    t.join();
    cout << a << endl;
    return 0;
}

关于 ref()C++ std::ref 详解

关于 C++ 中的右值和右值引用:

C++11右值引用(一看即懂)

深入浅出 C++ 11 右值引用

std::this_thread

this_thread 是一个命名空间,其包含如下 4 个工具函数:

thread::id get_id() noexcept;

void yield() noexcept;	// Yield to other threads

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 是一个 C++ 的时间库,seconds 是一个类模板的一种实例化(是一个类):

typedef duration < /*see rep below*/ > seconds;

duration 是一个类模板:

template <class Rep, class Period = ratio<1> >class duration;

其中,ratio 是一个表示分数的类模板:

template <intmax_t N, intmax_t D = 1> class ratio;

typedef std::ratio<1,3> one_third;		// 1/3
std::cout << "one_third= " << one_third::num << "/" << one_third::den << std::endl;

命名空间 this_thread 提供了 get_id() 函数用以获取线程id:

cout << this_thread::get_id() << endl;			//获取当前线程id
cout << t1.get_id() << endl;					//获取t1的id

std::mutex

class mutex;:用于加解锁的互斥类

创建五个子线程,模拟竞争进入临界区的情况:

void callback() {
	std::thread::id id = std::this_thread::get_id();
	std::cout <<id<<" entrying..." << std::endl;
	std::cout <<id<<" doing work" << std::endl;
	std::cout <<id<<" leveling..." << std::endl;
}

int main() {
	std::vector<std::thread> tv;
	for (int i = 0; i < 5; ++i) {
		tv.push_back(std::thread(callback));
	}
	
	for (int i = 0; i < 5; ++i) {
		tv[i].join();
	}
	return 0;
}

可以看出线程的执行循序杂乱无章:

1484 entrying...
3528 entrying...
3528 doing work
3528 leveling...
1484 doing work
1484 leveling...
10096 entrying...
10096 doing work
9868 entrying...
9868 doing work
9868 leveling...
10096 leveling...
4408 entrying...
4408 doing work
4408 leveling...

使用锁std::mutex进行线程互斥:

void callback() {
	g_lock.lock();
	std::thread::id id = std::this_thread::get_id();
	std::cout <<id<<" entrying..." << std::endl;
	std::cout <<id<<" doing work" << std::endl;
	std::cout <<id<<" leveling..." << std::endl;
	g_lock.unlock();
}

看起来输出有序了,但是这样做是不安全的,如果在解锁之前函数因为某种原因异常退出没有解锁,会导致后续线程无法再进入临界区

解决方案是采用lock_guard,利用Resource Acquisition Is Initialization特性

std::lock_guard

类模板:template <class Mutex> class lock_guard;

下面是一个 std::lock_guard 的模拟实现:

template<class Mutex> class LockGuard {
public:
	using mutex_type=Mutex;

	//构造时即加锁
	explicit LockGuard(Mutex& mtx) : m_mutex(mtx) {
		m_mutex.lock();
	}
	//析构时解锁
	~LockGuard() noexcept{
		m_mutex.unlock();
	}
	//禁止拷贝
	LockGuard(const LockGuard&) = delete;
	//LockGuard& operater=(const LockGuard&) = delete;

private:
	Mutex& m_mutex;
};

std::mutex g_lock;

void callback() {
	LockGuard<std::mutex> lock(g_lock);
	std::thread::id id = std::this_thread::get_id();
	std::cout <<id<<" entrying..." << std::endl;
	std::cout <<id<<" doing work" << std::endl;
	std::cout <<id<<" leveling..." << std::endl;
	//离开作用域,lock被析构,自动解锁
}

当然,lock_gurad是一个提供好的类,直接调用即可无需自己实现:

std::lock_guard<std::mutex> lock(g_mutex);   //加锁

std::atomic

类模板:Objects of atomic types contain a value of a particular type (T)

用于定义一个原子化(线程安全)的类型。例如下面的代码原本的 int n = 0 是不安全的,改为 atomic<int> n = 0 后线程安全:

// int n = 0;
atomic<int> n = 0;

void count10000() {
    for (int i = 0; i < 10000; i++)
        n++;
}

int main() {
    thread ths[100];
    for(thread& t : ths) {
        t = thread(count10000);
    }
    for(thread& t : ths) {
        t.join();
    }
    cout << n << endl;
    return 0;
}

std::async

函数模板:

template <class Fn, class... Args> 
future<typename result_of<Fn(Args...)>::type>
async(launch policy, Fn&& fn, Args&&... args);
int adder(int x, int y) {
    this_thread::sleep_for(chrono::seconds(1));
    return x + y;
}

int main() {
    int a{10}, b{20};
    auto f = async(launch::async, adder, a, b);
    cout << f.get() << endl;
    return 0;
}

async 是一个函数模板,它的第一个参数是启动策略,它控制 std::async 的异步行为,我们可以用三种不同的启动策略来创建std::async

  • std::launch::async:保证异步行为,即传递函数将在单独的线程中执行
  • std::launch::deferred:当其他线程调用get()来访问共享状态时,将调用非异步行为
  • std::launch::async | std::launch::deferred:默认行为。有了这个启动策略,它可以异步运行或不运行,这取决于系统的负载,但我们无法控制它

第二个参数传入一个可调用对象,后面传入可调用对象的参数,跟 thread() 类似

async() 的返回类型是 future,可以通过它的 get() 方法阻塞等待线程的执行结果

std::future

类模板:

template <class T>  future;
template <class R&> future<R&>;
bool is_prime (int x) {
    for (int i=2; i<x; ++i) if (x%i==0) return false;
    return true;
}

int main () {
    // call function asynchronously:
    std::future<bool> fut = std::async(is_prime, 444444443);

    // do something while waiting for function to set future:
    std::cout << "checking, please wait;";
    std::chrono::milliseconds span (100);
    std::string s = "|/-\\";
    int i = 0;
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << "\b" << s[(i++) % 4] << std::flush;
    bool x = fut.get();     // retrieve return value
    std::cout << "\n444444443" << (x?"is":"is not") << " prime\n";
    return 0;
}

wait_for(): Wait for ready during time span

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

可能的返回值有如下 3 种:

在这里插入图片描述
timeout 表示等待指定时长后任务尚未完成

参考文章:

C++11 多线程(std::thread)详解

相关推荐

  1. C++学习笔记线

    2024-03-18 00:32:04       31 阅读
  2. C++线学习笔记

    2024-03-18 00:32:04       27 阅读
  3. C++ 线笔记1 线的创建

    2024-03-18 00:32:04       29 阅读
  4. C++线学习笔记004简单的Producer和Consumer模型

    2024-03-18 00:32:04       54 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-18 00:32:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-18 00:32:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-18 00:32:04       82 阅读
  4. Python语言-面向对象

    2024-03-18 00:32:04       91 阅读

热门阅读

  1. 【C/C++ 学习笔记】内存

    2024-03-18 00:32:04       45 阅读
  2. 【C语言】等边等腰三角形的判断

    2024-03-18 00:32:04       40 阅读
  3. 【Git】git pull fatal: refusing to merge unrelated histories

    2024-03-18 00:32:04       46 阅读
  4. 【Vue2】v-model

    2024-03-18 00:32:04       43 阅读
  5. Git使用

    Git使用

    2024-03-18 00:32:04      33 阅读
  6. 2024年3月职业健康安全管理体系基础考试真题

    2024-03-18 00:32:04       43 阅读
  7. 智能车摄像头灰度处理高效算法(下)

    2024-03-18 00:32:04       46 阅读
  8. ThreadLocal-案例编码实战

    2024-03-18 00:32:04       41 阅读
  9. 17个工作必备的Python自动化代码分享(上篇)

    2024-03-18 00:32:04       34 阅读