首页 > 编程语言 >c++的线程初探-2

c++的线程初探-2

时间:2023-08-16 23:01:23浏览次数:34  
标签:变量 int lock c++ 线程 初探 mutex include

(目录)


一、条件变量

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。 C++11的条件变量提供了两个类:

  • condition_variable:只支持与普通mutex搭配,效率更高。
  • condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

生产消费问题的经典案例:

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <thread> //线程类头文件。
#include <mutex>  //互斤锁类的头文件
#include <deque>  //deque容器的头文件。
#include <queue>  //queue容器的头文件。
#include <condition_variable> //条件变量的头文件。

using namespace std;

class AA {
	mutex m_mutex;
	condition_variable m_cond;
	queue<string, deque<string>> m_q;
public:
	void incache(int num) { // 生产数据,num指定数据的个数
		lock_guard < mutex > lock(m_mutex); // 申请加锁。离开作用域的时候lock_guard会自动释放锁。
		for (int i = 0; i < num; ++i) {
			static int bh = 1; // 超女编号
			string message = to_string(bh++) + " data";
			m_q.push(message);
			cout << "producting thread: " << this_thread::get_id() << ", "
					<< message << endl;
		}
		// 唤醒一个被当前条件变量阻塞的消费线程
		//m_cond.notify_one();

		// 唤醒全部被当前条件变量阻塞的消费线程:让所有消费者线程都进来抢
		m_cond.notify_all();
	}

	void outchache() { // 消费数据
		while (true) {
			string message;
			{
				//cout << "consuming thread: " << this_thread::get_id() << ", "	<< "申请加锁" << endl;
				unique_lock < mutex > lock(m_mutex); // lock互斥锁在出作用域的时候也会释放
				//cout << "consuming thread: " << this_thread::get_id() << ", "	<< "加锁成功" << endl;

				// 挡住线程
				//this_thread::sleep_for(chrono::hours(1));
				// 存在条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。
				// 被虚假唤醒的消费者线程要通过while反复判断,然后再进入wait()。使用if语句达不到效果(错过时点)。
				while (m_q.empty())
					// 1. 把互斥锁m_mutex解锁 2. 阻塞,等待被唤醒。 3. 给互斥锁m_mutex加锁。
					m_cond.wait(lock);
				/**
				 * c++11为我们提供了与上述while循环等效的解决方案:
				 * m_cond.wait(lock, [this]{return !m_q.empty();});
				 */

				message = m_q.front();
				m_q.pop();
				cout << "consuming thread: " << this_thread::get_id() << ", "
						<< message << endl;
				//lock.unlock(); 手工解锁
			}

			// 模拟消费出队数据
			this_thread::sleep_for(chrono::milliseconds(1));
		}
	}
};

int main(int argc, const char **argv) {
	const int max = 8;
	srand(time(nullptr));

	AA aa;

	// 定义消费线程
	thread t1(&AA::outchache, &aa);
	thread t2(&AA::outchache, &aa);
	thread t3(&AA::outchache, &aa);

	this_thread::sleep_for(chrono::seconds(rand() % max));
	// 主线程生产数据
	aa.incache(6);

	// 主线程生产数据
	this_thread::sleep_for(chrono::seconds(rand() % max));
	aa.incache(8);

	// 回收线程资源
	t1.join();
	t2.join();
	t3.join();

	return 0;
}

普通的互斤锁为什么要转换成unique_lock之后才能用于条件变量呢?

unique_lock < mutex > lock(m_mutex);

template <class Mutex> class unique_lock 是模板类,模板参数为互斥锁类型。 unique_lock和lock_guard都是管理锁的辅助类,都是RAII风格(在构造时获得锁,在析构时释放锁)。它们的区别在于:为了配合condition_variable,unique_lock还有lock()和unlock()成员函数——而lock_guard没有。

二、wait()所做的工作

  1. 把互斥锁解锁
  2. 阻塞,等待被唤醒。
  3. 给互斥锁加锁。 如果wait()返回了,一定申请到了互斥锁。

三、原子类型atomic

c++11提供了atomic<T>模板类(结构体),用于支持原子类型,模板参数可以是bool、char、int、long、long long、指针类型(不支持浮点类型和自定义数据类型)——只支持整数。原子操作由CPU指令提供支持,它的性能比锁和消息传递更高,并且,不需要程序员处理加锁和释放锁的问题,支持修改、读取、交换、比较并交换等操作。 头文件:#include <atomic>

#include <iostream>
#include <thread> //线程类头文件。
#include <atomic>

using namespace std;

// 只支持整数
atomic<int> ga { 0 };
//atomic<int> ga(0);
//atomic_int ga(0);

void func() {
	for (int i = 0; i < 1000000; ++i)
		ga++;
}

int main(int argc, const char **argv) {
	thread t1(func);
	thread t2(func);

	t1.join();
	t2.join();

	cout << "ga = " << ga << endl;

	return 0;
}

另一个演示

#include <iostream>
#include <thread> //线程类头文件。
#include <atomic>

using namespace std;

int main(int argc, const char **argv) {
	atomic<int> a(3); // 转换函数
	cout << "a = " << a.load() << endl; // 读取原子变量a的值
	a.store(8); // 把8存储到原子变量中
	cout << "a = " << a.load() << endl; // 读取原子变量a的值

	int old;
	//用于存放原值
	old = a.fetch_add(5); //把原子变量a的值与5相加,返回原值
	cout << "old = " << old << ", a =" << a.load() << endl; // old=8, a=13
	old = a.fetch_sub(2); //把原子变量a的值减2,返回原值。
	cout << "old = " << old << ", a =" << a.load() << endl; // old=13, a=11

	atomic<int> ii(3); //原子变量
	int expect = 3; //期待值
	int val = 5; //打算存入原子变量的值
	//比较原子变量的值和预期值expect
	//如果当两个值相等,把val存储到原子变量中
	//小如果当两个值不相等,用原子变量的值更新预期值
	//执行存储操作时返回true,否则返回false。
	bool bret = ii.compare_exchange_strong(expect, val);
	cout << "bret=" << bret << endl;
	cout << "ii=" << ii << endl;
	cout << "expect=" << expect << endl;

	return 0;
}

比较原子变量的值和预期值expect,如果当两个值相等,把val存储到原子变量中,函数返回true;如果当两个值不相等,用原子变量的值更新预期值,函数返回false。CAS指令。

T compare_exchange_strong(T &expect,const T val)noexcept;
  • atomic<T> 模板类重载了整数操作的各种运算符。
  • atomic<T> 模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。
  • 原子整型可以用作计数器,布尔型可以用作开关。
  • CAS指令是实现无锁队列的基础。

四、线程函数的多种形式

#include <iostream>
#include <thread> //线程类头文件。

using namespace std;

void f() {
	cout << "void f() .." << endl;
}
struct F {
	F(int a) :
			x(a) {
	}
	void operator()() {
		cout << "F::operator() " << x << endl;
	}
	void f() {
		cout << "member f " << x << endl;
	}
	int x;
};

int main(int argc, const char **argv) {
	thread t1(f);
	thread t2(F(20));
	thread t3([] {
		cout << "noname func" << endl;
	});
	F a(10);
	thread t4(F::f, &a); // 相当于有参的函数

	t1.join();
	t2.join();
	t3.join();
	t4.join();

	return 0;
}

五、引用的传递

引用的传递有点不同,如果不加处理,编译器无法判断我们是想传引用,还是想传值。 可以这样处理:

#include <iostream>
#include <thread> //线程类头文件。

using namespace std;

void f(int &x) {
	this_thread::sleep_for(chrono::seconds(1));
	cout << x << endl;
}

int main(int argc, const char **argv) {
	int x = 5;
	thread td(f, std::ref(x));
	x = 20;
	td.join();

	return 0;
}

六、再议线程互斥

image.png

用锁原则总结:

  • 共享对象的操作要考虑加锁
  • 锁的颗粒度越小越好
  • 尽量不要用 mutex 裸装上阵,危险处处!
  • 如无特殊需要,用 lock_guard 即可
  • 如控制上满足不了要求(一般是粒度),改用unique_lock

七、局限于线程中的“全局变量” thread_local

#include <iostream>
#include <thread>

using namespace std;

thread_local int x = 0;

void g() {
	x++;
	cout << x << endl;
}

void f() {
	for (int i = 0; i < 10; i++)
		g();
}

int main(int argc, const char **argv) {
	thread t1(f);
	thread t2(f);
	t1.join();
	t2.join();
	cout << x << endl;

	return 0;
}

每个thread_local变量在线程创建的时候创建,然后对本线程而言,相当于全局变量;当线程销毁的时候,会同时销毁当初创建的这个thread_local变量。thread_local变量不是静态存储的。

七、c++11 future & promise

#include <iostream>
#include <thread>
#include <future>
#include <chrono>

using namespace std;

int func(int x, int y) {
	this_thread::sleep_for(chrono::seconds(5));
	return x * 100 + y;
}

int main(int argc, const char **argv) {
	// launch::async 表示创建新线程;
	// launch::deferred 表示延迟处理,同步调用,不创建新线程;
	// launch::async | launch::deferred 表示系统自己看着办
	future<int> res = async(launch::async, func, 3, 6); // 如果线程中抛出了异常,可以在主线程中抓住,
	cout << "do something else ..." << endl;
	int r = res.get(); // func计算未完成就阻塞在这里,直到func返回结果。
	cout << r << endl;

	return 0;
}

标签:变量,int,lock,c++,线程,初探,mutex,include
From: https://blog.51cto.com/cerana/7114813

相关文章

  • 【转载】JUC常见面试题:Java线程和操作系统线程有什么区别
    【转载】JUC常见面试题:Java线程和操作系统线程有什么区别整理来自于:https://cloud.tencent.com/developer/article/18181511操作系统的用户态和核心态在操作系统中,内存通常会被分成用户空间(Userspace)与内核空间(Kernelspace)这两个部分。当进程/线程运行在用户空间时就处于用......
  • 网络编程day04--线程管理
    线程管理基本概念1、线程是进程的执行路线,它是进程内部的控制序列,线程是进程的一部分,进程是一个资源单位,而线程是执行单位,线程是进程执行的实体,负责真正的执行2、线程是轻量级的,没有自己的代码段、数据段、bss段、堆、环境变量、命令行参数、文件描述符、信号处理函数、当前工......
  • (随笔)龟兔赛跑(多线程调用同一资源时一个线程结束时其他线程保持运行)
    问题:当其一线程结束运行后其他线程保持运行而非结束现象:控制台会输出两次thewinneris:xxx代码如下packagecom.demo01;/***TODO模拟归途赛跑**@authorpangyangjian*@since2023/8/1616:10*/publicclassTextThread_5implementsRunnable{ @Override......
  • 多线程&异步编程
    多线程&网络编程(异步编程)1)重要性,高并发,短时间内遇到大量请求2)难度硬件.操作系统多线程本身的复杂性,死锁,资源抢占,线程同步...--->多线程进程:一般指程序中运行的程序,实际作用是为程序在执行过程中创建好所需要的环境和资源.线程:是进程的一个实体,是Cpu用来调度......
  • C++ Constructor And Destructor
    ifyouhaveproblemswithconstructorsanddestructors,youcaninsertsuchprintstatementsinconstructorsforyourrealclassestoseethattheyworkasintended.Forlargerprograms,thisexactkindoftracingbecomestedious,butsimilartechniquesa......
  • 【转载】c++调用win32API控制打印机打印
    原文:https://blog.csdn.net/cheng448208985/article/details/55510687win32实现将原始数据发送给打印机1、调用OpenPrinter()打开打印机,获取打印机句柄。2、初始化DOCINFO打印机结构体。3、调用StartDocPrinter()表明应用程序准备发送文档数据给打印机。4、调用StartPagePrin......
  • C++ Cast And Go Cast
    C++Astatic_castcanbeusedtoexplicitlyconvertbetweenrelatedpointertypes,suchasvoid*anddouble*reinterpret_castcancastbetweenunrelatedtypes,suchasintanddouble*.const_castcan“castawayconst.”Forexample:int*pi=static_ca......
  • jquery mobile 初探
    现在已经进入了移动web时代。所以现在的mobile的js框架也开始流行。浏览器有一个好处:不用区分安卓还是iOS,也不用下载app。随着框架和控件的日益增多,应用将更加丰富。比较著名的如:jquerymobile,Moobile(基于mooltools框架)TheMProject,senchatouch(继承ExtJS4的应用程序MVC架构),Ti......
  • 多线程|线程的特性
      ......
  • Knockout.js初探
    Knockout是一个轻量级的js的UI类库,通过应用MVVM模式(Model-View-ViewModel,MVP是用在某个特定页面上,WPF技术出现,使得MVP晋级成MVVM。模式也是依次进化而形成MVC—>MVP—>MVVM。WPF就是WindowsVista的用户界面框架,属于NETFramework3.0的一部分。)使JavaScript前端UI简单化。Knockout......