1. 线程(thread)
c++11加入了thread线程类,使我们在操作多线程的时候更加的简单;
线程是调度的基本单位
进程则是资源拥有的基本单位
头文件:
#include <thread>
下面通过一个例子,来说明线程的创建与使用
- join:调用该函数会阻塞当前线程,需要等待子线程执行完毕;
- joinable:用来判断线程是否可以join,返回值为bool类型;
- detach:将线程分离出来,不再和主线程相关,可以单独存在,非必要,不建议使用;
- swap:交换两个线程对象所代表的底层句柄;
- get_id:返回线程id
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <unistd.h>
void printA()
{
int32_t a = 1;
while(1)
{
std::cout<<"thread A"<<std::endl;
if(a++ > 10) break;
}
}
void printB()
{
int32_t b = 1;
sleep(1);
while(1)
{
std::cout<<"thread B"<<std::endl;
if(b++ > 10) break;
}
}
int main(int argc, char *argv[])
{
std::thread t1{printA}; //创建线程,并初始化
std::thread t2(printB); //创建线程,并初始化
std::cout<<"t1 is: " <<t1.get_id()<<std::endl; //输出线程id
std::cout<<"t2 is: " <<t2.get_id()<<std::endl;
std::swap(t1,t2); //交换
std::cout<<"t1 is: " <<t1.get_id()<<std::endl;
std::cout<<"t2 is: " <<t2.get_id()<<std::endl;
sleep(2);
std::thread t3; //创建线程
//通过lambda初始化线程
t3 = std::thread([](){
int32_t c = 1;
while(1){
std::cout<<"thread C"<<std::endl;
if(c++ > 10) break;
}
});
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
if(t3.joinable())
{
t3.join();
}
return 0;
}
输出
t1 is: 140476317243136
t2 is: 140476308850432
t1 is: 140476308850432
t2 is: 140476317243136
...
g++编译方法(需要带上-pthread)
g++ -g main.cc -o d -std=c++11 -pthread
2. 锁(mutex)
锁的出现是为了防止资源竞争,锁越复杂,程序的性能就越低;
头文件
#include <mutex>
mutex中锁的分类
std::mutex 互斥锁
std::recursive_mutex 递归锁
std::timed_mutex 定时锁
std::recursive_timed_mutex 定时递归锁
C++还提供了类模板,方便我们使用
Lock 类
std::lock_guard RAII机制下的锁,模板类,在创建的时候自动加锁,在销毁的时候自动解锁,优点是简单,直接加锁就可以,缺点是不灵活
std::unique_lock RAII机制下的锁,模板类,是对lock_guard的扩展,支持自己上锁和解锁,支持以下方法
lock()
unlock()
try_lock()
try_lock_for()
try_lock_until()
unique_lock<mutex> uniqeLock(mu, std::adopt_lock); //同lock_guard, 指示不加锁,前提是mutex已经加锁
unique_lock<mutex> uniqeLock(mu, std::try_to_lock); //尝试加锁,用在获取不到锁时执行一些其他操作
unique_lock<mutex> uniqeLock(mu, std::defer_lock); //不加锁,且mutex尚未锁定,配合成员函数使用
uniqeLock.try_lock(); //同参数std::try_to_lock
uniqeLock.owns_lock(); //返回bool,表明是否拥有锁
uniqeLock.release(); //返回mutex,解除unique_lock与mutex的绑定,unique_lock为空
2.1 互斥锁
互斥锁,同一时刻只允许一个线程访问,其余线程则等待
示例1
通过 std::mutex g_mutex 自动的上锁和解锁;
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <chrono>
#include <condition_variable>
std::mutex g_mutex;
int32_t g_count{0};
void calFun1(int32_t n)
{
g_mutex.lock();
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
g_mutex.unlock(); //一定要记得解锁
std::cout<<"calFun1 g_count is: "<<g_count<<std::endl;
}
void calFun2(int32_t n)
{
g_mutex.lock();
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
g_mutex.unlock(); //一定要记得解锁
std::cout<<"calFun2 g_count is: "<<g_count<<std::endl;
}
int main(int argc, char *argv[])
{
std::thread t1{calFun1, 1000000};
std::thread t2(calFun2, 1000000);
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
std::cout<<"g_count is: "<<g_count<<std::endl;
return 0;
}
输出
//不加锁输出
calFun1 g_count is: 1029002
calFun2 g_count is: 1124281
g_count is: 1124281
//加锁输出
calFun1 g_count is: 1000000
calFun2 g_count is: 2000000
g_count is: 2000000
示例2
通过C++提供的模板类,std::lock_guard和std::unique_lock会自动的完成解锁,一般情况下会使用std::lock_guard,其效率更高
std::mutex g_mutex;
std::lock_guard<std::mutex> m(g_mutex);
std::unique_lock<std::mutex> m(g_mutex);
示例
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <unistd.h>
std::mutex g_mutex;
int32_t g_count(0);
void calFun1(int32_t n)
{
std::lock_guard<std::mutex> m(g_mutex);
//std::unique_lock<std::mutex> m(g_mutex);
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun1 g_count is: "<<g_count<<std::endl;
}
void calFun2(int32_t n)
{
std::lock_guard<std::mutex> m(g_mutex);
//std::unique_lock<std::mutex> m(g_mutex);
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun2 g_count is: "<<g_count<<std::endl;
}
int main(int argc, char *argv[])
{
std::thread t1{calFun1, 1000000};
std::thread t2(calFun2, 1000000);
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
std::cout<<"g_count is: "<<g_count<<std::endl;
return 0;
}
输出
calFun1 g_count is: 1000000
calFun2 g_count is: 2000000
g_count is: 2000000
示例3(定时锁)
定时锁,设置线程的等待时间,如果超时则进行其它处理;
单独使用std::timed_mutex g_mutex,需要解锁
设置calFun1的等待时间,如果超过3秒,则返回
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <chrono>
std::timed_mutex g_mutex;
int32_t g_count{0};
void calFun1(int32_t n)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if(!g_mutex.try_lock_for(std::chrono::seconds(3)))
{
std::cout<<"calFun1 connot get lock!"<<std::endl;
return;
}
else{
std::cout<<"false"<<std::endl;
}
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
g_mutex.unlock(); //需要解锁
std::cout<<"calFun1 g_count is: "<<g_count<<std::endl;
}
void calFun2(int32_t n)
{
std::unique_lock<std::timed_mutex> m(g_mutex);
std::this_thread::sleep_for(std::chrono::seconds(5));
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun2 g_count is: "<<g_count<<std::endl;
}
int main(int argc, char *argv[])
{
std::thread t1{calFun1, 1000000};
std::thread t2(calFun2, 1000000);
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
std::cout<<"g_count is: "<<g_count<<std::endl;
return 0;
}
输出
calFun1 connot get lock!
calFun2 g_count is: 1000000
g_count is: 1000000
示例4(定时锁)
通过模板类std::unique_lock来实现
修改calFun1
void calFun1(int32_t n)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::timed_mutex> mt(g_mutex, std::defer_lock); //这里需要使用参数std::defer_lock,不加锁
if(!mt.try_lock_for(std::chrono::seconds(3)))
{
std::cout<<"calFun1 connot get lock!"<<std::endl;
return;
}
else{
std::cout<<"false"<<std::endl;
}
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun1 g_count is: "<<g_count<<std::endl;
}
2.2 条件锁
条件锁即为条件变量+互斥锁,当线程未满足运行条件时,则阻塞;只有满足运行条件,才会运行;
使用条件变量的头文件
#include <condition_variable>
- notify_one:唤醒一个线程,如果线程有多个,则唤醒哪一个线程是随机的
- notify_all:唤醒所有线程
- wait:等待直到条件满足
- wait_for:等待直到条件满足 或 达到超时时间(时间长度)
- wait_until:等待直到条件满足 或 达到设定的超时时间(时间点)
示例
其中有两个给线程,一个线程来来自增全局变量g_count,当除以10余数为0的时候,通知另外一个线程来打印全局变量g_count。
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <condition_variable>
bool flag = false;
bool exit_flag = false;
std::mutex g_mutex;
int32_t g_count(0);
std::condition_variable cv; //条件变量
void calFun1(int32_t n)
{
std::unique_lock<std::mutex> lck(g_mutex);
for(int32_t i = 0; i < n ;i++)
{
if(g_count%10 == 0)
{
flag = true;
cv.notify_one();
cv.wait(lck,[]{return !flag;}); //等待,后面跟lambda表达式
}
g_count++;
}
exit_flag = true;
flag = true;
cv.notify_one();
}
void calFun2(int32_t n)
{
while(1)
{
std::unique_lock<std::mutex> lck(g_mutex);
cv.wait(lck, []{return flag;});
std::cout<<"g_count value is: "<<g_count<<std::endl;
flag = false;
cv.notify_one();
if(exit_flag) break;
}
std::cout<<"calFun2 exit"<<std::endl;
}
int main(int argc, char *argv[])
{
std::thread t1{calFun1, 100};
std::thread t2(calFun2, 100);
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
std::cout<<"g_count is: "<<g_count<<std::endl;
return 0;
}
输出
g_count value is: 0
g_count value is: 10
g_count value is: 20
g_count value is: 30
g_count value is: 40
g_count value is: 50
g_count value is: 60
g_count value is: 70
g_count value is: 80
g_count value is: 90
g_count value is: 100
calFun2 exit
g_count is: 100
2.3 自旋锁
互斥锁在访问资源的时候,如果有锁则会等待,这个时候cpu的资源会释放;自旋锁在获取资源的时候,如果有锁,则会一直尝试,直到访问到资源,这个时候cpu的资源是不会被释放的;
自旋锁需要自己实现
示例:
class spinlock_mutex
{
public:
spinlock_mutex() {};
~spinlock_mutex() {};
void lock()
{
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
使用
spinlock_mutex sp_mutex; //外部定义
std::lock_guard<spinlock_mutex> m(sp_mutex); //在使用的时候定义
2.4 读写锁
读写锁,在读取资源的时候,不独占资源,可以共享;但是在进行写操作的时候,需要独占;
以下代码来自网络读写锁
读写锁在C++17增加了shared_mutex,使用起来更加方便
class readWriteLock {
private:
std::mutex readMtx;
std::mutex writeMtx;
int readCnt; // 已加读锁个数
public:
readWriteLock() : readCnt(0) {}
void readLock()
{
readMtx.lock();
if (++readCnt == 1) {
writeMtx.lock(); // 存在线程读操作时,写加锁(只加一次)
}
readMtx.unlock();
}
void readUnlock()
{
readMtx.lock();
if (--readCnt == 0) { // 没有线程读操作时,释放写锁
writeMtx.unlock();
}
readMtx.unlock();
}
void writeLock()
{
writeMtx.lock();
}
void writeUnlock()
{
writeMtx.unlock();
}
};
2.5 递归锁
假设定了锁std::mutex m_mutes,函数func1和func2中都加了锁,同时func1中调用了func2,这种情况下普通的锁会出现死锁,而递归锁可以解决该问题;
递归锁
std::recursive_mutex r_mutes;
示例
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include <vector>
#include <shared_mutex>
#include <unistd.h>
std::atomic<int32_t> g_count{0};
std::recursive_mutex r_mutes;
std::mutex m_mutes;
void func2()
{
//std::lock_guard<std::mutex> m(m_mutes); //该锁会形成死锁
std::lock_guard<std::recursive_mutex> m(r_mutes);
g_count++;
std::cout<<"(fucn2) g_count value is: "<<g_count<<std::endl;
}
void func1(int32_t n)
{
for(int i = 0;i<n;i++)
{
//std::lock_guard<std::mutex> m(m_mutes); //该锁会形成死锁
std::lock_guard<std::recursive_mutex> m(r_mutes);
g_count++;
std::cout<<"(fucn1) g_count value is: "<<g_count<<std::endl;
func2();
}
}
int main(int argc, char *argv[])
{
std::thread t1{func1,3};
if(t1.joinable())
{
t1.join();
}
return 0;
}
输出
(fucn1) g_count value is: 1
(fucn2) g_count value is: 2
(fucn1) g_count value is: 3
(fucn2) g_count value is: 4
(fucn1) g_count value is: 5
(fucn2) g_count value is: 6
也可以自己实现递归锁,其思想为加锁和解锁的计算;
RecursiveMutex类的实现,其思路来自网上(RecursiveMutex)
在使用的时候需要自己去lock()和unlock(),不能使用模板类
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include <vector>
#include <shared_mutex>
#include <unistd.h>
std::atomic<int32_t> g_count{0};
std::recursive_mutex r_mutes;
std::mutex m_mutes;
class RecursiveMutex {
public:
RecursiveMutex() {
num_of_locks = 0;
}
~RecursiveMutex() {}
void lock() {
if (num_of_locks == 0) {
my_mutex.lock();
owner_thread_id = std::this_thread::get_id();
}
else if (std::this_thread::get_id() == owner_thread_id)
num_of_locks++;
}
void unlock() {
if (num_of_locks > 0)
num_of_locks--;
if (num_of_locks == 0)
my_mutex.unlock();
}
private:
int num_of_locks;
std::mutex my_mutex;
std::thread::id owner_thread_id;
};
RecursiveMutex r_mutex;
void func2()
{
r_mutes.lock();
g_count++;
std::cout<<"(fucn2) g_count value is: "<<g_count<<std::endl;
r_mutes.unlock();
}
void func1(int32_t n)
{
for(int i = 0;i<n;i++)
{
r_mutes.lock();
g_count++;
std::cout<<"(fucn1) g_count value is: "<<g_count<<std::endl;
func2();
r_mutes.unlock();
}
}
int main(int argc, char *argv[])
{
std::thread t1{func1,3};
if(t1.joinable())
{
t1.join();
}
return 0;
}
2.6 共享锁
C++17才开始支持
头文件
是读写锁
#include <shared_mutex>
使用
std::shared_mutex shared_lock;
std::shared_lock<std::shared_mutex> m(shared_lock);
示例
//g++ -g main_shared.cc -o d -std=c++17 -pthread
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <unistd.h>
std::atomic<int32_t> g_count{0};
std::shared_mutex shared_lock;
void readValue(int32_t n, std::string str)
{
//std::lock_guard<std::shared_mutex> m(shared_lock);
std::shared_lock<std::shared_mutex> m(shared_lock);
for(int i = 0; i < n; i++)
{
std::cout<<str <<" g_count value is: "<<g_count<<std::endl;
}
}
void writeValue(int32_t n, std::string str)
{
//std::lock_guard<std::shared_mutex> m(shared_lock);
std::shared_lock<std::shared_mutex> m(shared_lock);
for(int i = 0; i < n; i++)
{
g_count++;
std::cout<<str <<" g_count value is: "<<g_count<<std::endl;
}
}
int main(int argc, char *argv[])
{
std::vector<std::thread> v;
v.emplace_back(std::thread{readValue,3,"t1"});
v.emplace_back(std::thread{readValue,3,"t2"});
v.emplace_back(std::thread{writeValue,3,"t3"});
v.emplace_back(std::thread{writeValue,3,"t4"});
v.emplace_back(std::thread{writeValue,3,"t5"});
v.emplace_back(std::thread{writeValue,3,"t6"});
for(int32_t i =0; i< v.size();i++)
{
if(v[i].joinable())
{
v[i].join();
}
}
return 0;
}
输出
t1 g_count value is: t2 g_count value is: 00
t1 g_count value is: 0
t1 g_count value is: 0
t2 g_count value is: 0
t2 g_count value is: 0
t3 g_count value is: 1
t3 g_count value is: 2
t3 g_count value is: 3
t4 g_count value is: 4
t4 g_count value is: 5
t4 g_count value is: 6
t6 g_count value is: 7
t6 g_count value is: 8
t6 g_count value is: 9
t5 g_count value is: 10
t5 g_count value is: 11
t5 g_count value is: 12
3. 原子操作
感觉原子操作的内容比较多,本文只记录std::atomic和std::atomic_flag的常用方法;
原子操作可以实现无锁,当然也可以用于实现锁、条件变量等多线程同步机制;
头文件:
#include <atomic>
支持版本:C++11
std::atomic
std::atomic
std::atomic<Integral>
支持类型(支持的类型较多,并且随着版本的迭代,支持的类型越来越多)
1. std::atomic<bool>
2. std::atomic<char>
3. std::atomic<signed char>
4. std::atomic<unsigned char>
5. std::atomic<short>
6. std::atomic<unsigned short>
7. std::atomic<int>
8. std::atomic<unsigned int>
9. std::atomic<long>
10. std::atomic<unsigned long>
11. std::atomic<long long>
12. std::atomic<unsigned long long>
支持的成员函数
store() //用非原子参数替换原子参数的数值
load() //获得原子对象的值
exchange() //替换原子对象的值
compare_exchange_weak() // 原子对象和非原子对象进行比较,相等则修改原子对象的值(比较值和修改值是两个),不相等则不赋值
compare_exchange_strong() //同上
//compare_exchange_weak比compare_exchange_strong性能更高一点
无锁示例
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
std::atomic<int32_t> g_count{0};
void calFun1(int32_t n)
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun1 g_count is: "<<g_count<<std::endl;
}
void calFun2(int32_t n)
{
for(int32_t i = 0; i < n ;i++)
{
g_count++;
}
std::cout<<"calFun2 g_count is: "<<g_count<<std::endl;
}
int main(int argc, char *argv[])
{
std::thread t1{calFun1, 1000000};
std::thread t2(calFun2, 1000000);
if(t1.joinable())
{
t1.join();
}
if(t2.joinable())
{
t2.join();
}
std::cout<<"g_count is: "<<g_count<<std::endl;
return 0;
}
输出
也会出现资源竞争,但是输出结果无误
calFun1 g_count is: 1997543
calFun2 g_count is: 2000000
g_count is: 2000000
成员函数的使用示例
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
std::atomic<int32_t> g_count{0};
int main(int argc, char *argv[])
{
g_count = 10;
g_count.store(15); //替换其值
std::cout<<"set value by store()\t\t"<<g_count<<std::endl;
std::cout<<"get value by load()\t\t"<<g_count.load()<<std::endl;
//g_count.load(std::memory_order_seq_cst);
//g_count.exchange(100, std::memory_order_seq_cst);
g_count.exchange(98);
//g_count.exchange(99);
std::cout<<"change value by exchange()\t"<<g_count<<std::endl<<std::endl;
/*
* 判断逻辑
* g_count == expected_value? (return true; g_count = new_value) : (return false; expected_value = g_count)
*/
int32_t expected_value = 99;
int32_t new_value = 88;
if(g_count.compare_exchange_weak(expected_value, new_value))
{
std::cout<<"compare_exchange_strong true."<<std::endl;
}
else{
std::cout<<"compare_exchange_strong false."<<std::endl;
}
std::cout<<"new_value is: \t\t"<<new_value<<std::endl;
std::cout<<"expected_value is: \t"<<expected_value<<std::endl;
std::cout<<"g_count is: \t\t"<<g_count<<std::endl<<std::endl;
/*
* 判断逻辑
* g_count == expected_value? (return true; g_count = new_value) : (return false; expected_value = g_count)
*/
//g_count.store(998);
g_count.store(999);
expected_value = 999;
new_value = 888;
if(g_count.compare_exchange_strong(expected_value, new_value))
{
std::cout<<"compare_exchange_strong true."<<std::endl;
}
else{
std::cout<<"compare_exchange_strong false."<<std::endl;
}
std::cout<<"new_value is: \t\t"<<new_value<<std::endl;
std::cout<<"expected_value is: \t"<<expected_value<<std::endl;
std::cout<<"g_count is: \t\t"<<g_count<<std::endl<<std::endl;
return 0;
}
输出
set value by store() 15
get value by load() 15
change value by exchange() 98
compare_exchange_strong false.
new_value is: 88
expected_value is: 98
g_count is: 98
compare_exchange_strong true.
new_value is: 888
expected_value is: 999
g_count is: 888
g_count.exchange(100, std::memory_order_seq_cst);
std::memory_order_seq_cst为枚举std::memory_order中的元素,可以设置原子操作对内存访问进行排序;
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
std::atomic_flag
std::atomic_flag是最简单的标准原子类型,表示一个布尔标志,该类型的对象只有两种状态:成立或置零;
对象必须由宏 ATOMIC_FLAG_INIT 初始化(置零)
支持销毁、置零、读取原有的值并设置标志成立,分为为析构函数、成员函数clear()、成员函数test_and_set()
test_and_set() //获取std::atomic_flag的状态并将其置为true