存档

文章标签 ‘c++11’

c++11特性:使用条件变量condition_variable进行线程间通信

2020年6月9日 评论已被关闭

多线程编程要实现的一个目的就是,线程间协同工作,以提高效率。

既然要协同,相互之间需要有一个通信机制,互通有无。

c++11提供了条件变量condition_variable来实现线程间通信。

线程可以通过条件变量来wait某个事件,其他线程准备好相关数据后,可通过notify唤醒正在等待的线程。

condition_variable需要和一个mutex关联使用。

请看示例代码:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
using namespace std;


queue<int> msglist;
mutex mmsg;
condition_variable cv;

void produce_msg(){
	for(auto i=0; i<100; i++){
		{
			unique_lock<mutex> lk{mmsg};
			msglist.push(i);
		}

		cout << "Producer put a message: " << i << endl;
		cv.notify_all();
	}
}

void consume_msg(const string& cname){
	while(true){
		{
			unique_lock<mutex> lk{mmsg};
			while(msglist.size() == 0){ //must be a loop
				cv.wait(lk);
			}

			cout << "Consumer-" << cname << " woke up." << endl;
			auto i = msglist.front();
			msglist.pop();
			cout << "Consumer-" << cname << " got message: " << i << endl;
		}
		this_thread::sleep_for(chrono::seconds{1});
	}
}

int main(){
	thread tp{produce_msg};
	thread tc1{consume_msg, "C1"};
	thread tc2{consume_msg, "C2"};

	tc1.join();
	tc2.join();

	tp.join();

	return 0;
}

我们在main中定义了三个线程,tp为生产者,tc1和tc2为消费者。
这三个线程共同存取队列msglist,所以需要通过互斥量mmsg进行同步。
消费者线程启动后,通过条件变量cv.wait()实现阻塞,等待新消息的产生。wait()会释放持有的互斥量mmsg,直到被唤醒时重新加锁。
生产者线程tp向msglist放入一个消息后,就释放互斥量,并通过条件变量cv.notify_all()唤醒两个消费者。
两个消费者被唤醒后,会对互斥量进行竞争性加锁,加锁成功者从队列msglist中取的消息,并短暂sleep,以便另一个消费者有机会获取消息。

需要注意,在consume_msg中wait()是置于一个循环中的。

这样做是为了避免多个多个消费者被同时唤醒时,第一个消费者将队列中的消息取出后,后边的消费者不加判断就去取消息,很可能会取空的现象。

这就是通过条件变量进行线程间通信的基本方法。

分类: C/C++, 开发 标签: ,

c++11特性:线程库thread的使用方法

2020年6月8日 评论已被关闭

多线程是提高程序吞吐量和响应时间的一个基本方法。

不同平台都提供了多线程支持,虽然实现原理和使用方法基本相同,但是由于接口不一致,应用程序需要对各线程库进行封装,以屏蔽平台接口差异,实现代码的跨平台运行。

c++11为我们提供了平台统一的多线程接口,有了它,我们可以方便地开发出跨平台的多线程应用程序。

c++11提供的线程对象包括

  • thread:创建一个线程对象,并提供线程控制接口
  • mutex 和 unique_lock:互斥量和锁,用于同步对共享数据的访问

我们通过示例来看一下线程库的基本用法。

注意:通过g++编译多线程程序时,需要指定-pthread参数。否则会报如下错误:

Enable multithreading to use std::thread: Operation not permitted

  • 简单的多线程:各行其事
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //no lock
    void simple(const string& who){
    	for(auto i=0; i<100; i++){
    	      cout << who << " is counting: " << i << endl;
    	      this_thread::sleep_for(chrono::milliseconds(10));
    	}
    	cout << who << " count Done." << endl;
    }
    
    int main(){
    	thread t1{simple, "T1"};
    	thread t2{simple, "T2"};
    
    	t1.join();
    	t2.join();
    
    	return 0;
    }
  • 基本的同步操作:“同时”操作一个变量
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    mutex m0;
    int count=9000;
    void f0(){
    	for(auto i=0; i<100; i++){
    	    {
    			unique_lock<mutex> lk{m0};
    			cout << ++count << endl;
    	    }
    
    	    this_thread::sleep_for(chrono::milliseconds(10));
    	}
    }
    
    int main(){
    	thread ta{f0};
    	thread tb{f0};
    
    	ta.join();
    	tb.join();
    
    	return 0;
    }
  • 死锁:
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //dead lock
    mutex m1, m2;
    
    void f1(const string& tname){
    	unique_lock<mutex> lk1{m1};
    	this_thread::sleep_for(chrono::seconds{5});
    	cout << "Thread " << tname << " woke up." << endl;
    
    	unique_lock<mutex> lk2{m2};
    	cout << "Thread " << tname << " Done." << endl;
    }
    
    void f2(const string& tname){
    	unique_lock<mutex> lk2{m2};
    	this_thread::sleep_for(chrono::seconds{5});
    	cout << "Thread " << tname << " woke up." << endl;
    
    	unique_lock<mutex> lk1{m1};
    	cout << "Thread " << tname << " Done." << endl;
    }
    
    int main(){
    	thread t3{f1, "T3"};
    	thread t4{f2, "T4"};
    	
    	t3.join();
    	t4.join();
    
    	return 0;
    }
  • 避免死锁:一次性获取多个互斥量
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //multiple lock
    mutex m3, m4;
    
    void f3(const string& tname, int s){
    	unique_lock<mutex> lk3{m3, defer_lock};
    	unique_lock<mutex> lk4{m4, defer_lock};
    
    	lock(lk3, lk4);
    	this_thread::sleep_for(chrono::seconds{s});
    	cout << "Thread--" << tname << " has waken up." << endl;
    }
    
    int main(){
    	thread t5{f3, "T5", 6};
    	thread t6{f3, "T6", 10};
    
    	t5.join();
    	t6.join();
    
    	return 0;
    }
分类: C/C++, 开发 标签: ,

c++11特性:统一初始化列表

2020年6月7日 评论已被关闭

在c++98标准中,对不同类型的变量有着不同的初始化方式。

比如:

#include <vector>
using namespace std;

class CTest{
  public:
    CTest():m_i(10){}
    CTest(int i):m_i(i){}

  private:
    int m_i;
};

int main(){
    int i=2020;
    int j=20.2;
    int a[5]={1,2,3,4,5};
    
    char sz[10]={"hello"};

    vector<int> va, vb(10, 8);

    CTest ta, tb(10);

    return 0;

}

上边这个例子就可以看到至少有3中初始化方式:=、{}和函数调用()。
而且对j的初始化,还发生了隐式类型转换,确切地说是把20.2给截断了。

c++11为变量提供了统一的初始化方式:初始化器列表,即{}。

我们可以用{}语法来初始化所有类型的变量。比如:

#include <vector>
#include <iostream>
using namespace std;

class CTest{
  public:
    CTest():m_i(10){}
    CTest(int i):m_i(i){}

  private:
    int m_i;
};

int main(){
    int i{2020};
    int j{20.2}; //compile warning here
    int a[5]={1,2,3,4,5};
    
    char sz[10]={"hello"};

    vector<int> va, vb(10, 8);

    CTest ta{}, tb{10};
    
    cout << j << endl;

    return 0;
}

一个{}初始化所有类型变量,统一起来更方便。
而且对于类型的窄化转换还会给出告警,甚至报错,减少bug的产生。

这是提升开发效率的一个重要改善。

ps:c++11支持4种形式的初始化器,如下所示:

T a{v};     //优先考虑
T a={v};   //多于的=
T a=v;      //最传统的内置数据类型初始化方式
T a(v);      //用户自定义类型,如struct、class初始化方式
分类: C/C++, 开发 标签: ,

c++11特性:无序关联容器unordered_map

2020年6月7日 评论已被关闭

map是c++提供的关联容器,可方便地存取键值对。

比如:

map<int, string> IdNameMap{{1, "Zhao"}, {3, "Sun"}, {2, "Qian"}};

map底层实现为红黑树,它是一种自平衡的二叉排序树。map中的元素会按照键的值进行“排序”。

对于上边的这个IdNameMap,对其遍历的话,会输出:

1 Zhao
2 Qian
3 Sun

这恰好能满足我们需要对数据进行排序的应用需求,而且足够高效。
但有时候我们可能不需要对数据进行排序,而仅仅是通过key能“快速”获取value,比二叉排序树更快一些。

c++11为我们提供了无序容器:unordered_map、unordered_set等。
unordered_map用法和map一致,其内部使用的是哈希算法,所以“更快”一些。

unordered_map<int, string> IdNameUMap{{1, "Zhao"}, {3, "Sun"}, {2, "Qian"}};

对这个IdNameUMap进行遍历,其输出可能是:
2 Qian
1 Zhao
3 Sun
和插入顺序可能不一样,也和key的大小顺序可能不同。

分类: C/C++, 开发 标签: ,

c++11特性:范围for

2020年6月7日 评论已被关闭

c++中,for用于对数组或容器的循环遍历。

我们一般需要先定义一个索引变量或者迭代器,通过累加索引变量或迭代器,并通过索引变量或迭代器来访问其指向的元素。

比如:

int ai[10]={0};
for(int i=0; i<10; i++){
    cout << ai[i];
}

vector<int> vi{1,2,3};
vector<int>::iterator it;
for(it=vi.begin(); it!=vi.end(); it++){
   cout << *it;
}

在这里,变量i和it用于迭代计数,我们还要借助于这两个迭代器间接访问数组或容器中的元素。

c++11对for的迭代功能进行了扩展,既不需要显式计数,又可以直接访问容器中的元素。
比如:

vector<int> vi{1,2,3};
for(int i:vi){
    cout << i << endl;
}

这里,我们定义的i的类型不再是迭代器,而是容器中元素的类型。for会自动进行迭代,将每个元素赋值给i。
我们可以通过i直接获取到容器中的元素。
当然,也可以令i为引用类型:int& i:vi。这样就可以直接修改容器中的元素的值了。

分类: C/C++, 开发 标签: ,

c++11特性:使用auto自动推导变量类型

2020年6月7日 评论已被关闭

c++是一个静态类型语言,每个变量声明时需要指明其类型,比如:

int a;
char c;

变量是什么类型,清醒明了。

c++11扩展了auto对变量类型的声明功能。
auto能在编译时通过变量的取值来确定变量的真实类型。比如:

auto i=10;  //int
auto c='c';  //char

这在一定场景下可以提高代码的灵活性。比如:

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
void print_elements(vector<T> v){
    for(auto & i:v){
        cout << i << endl;
    }
}

int main(){
    vector<int> vi{1, 2, 3};
    vector<char> vc{'a', 'b', 'c'};
    print_elements(vi);
    print_elements(vc);

    return 0;
}

在模板函数print_elements中,auto & i可以自动推断i的类型,从而实现了代码的统一。

分类: C/C++, 开发 标签: ,