目录
C++线程同步
引入
互斥锁
std::mutex
std::lock_guard类模板
unique_lock
成员方法
应用举例
std::lock()函数模板
std::call_once(flag、function)
懒汉式单例模式实例
unique_lock互斥锁方式
编辑
call_once方式
条件变量
std::condition
条件变量使用实例
原子操作
读写atomic对象的值
原子操作使用实例
内存模型:强顺序与弱顺序
线程同步是一种编程技术,它用于在多线程环境中确保多个线程能够正确、安全地共享和访问共享资源。线程同步的主要目的是防止数据竞争和不一致性,以及避免多个线程同时对同一数据进行修改或访问导致的问题。
以下是一些需要实现线程同步的情况:
因此,实现线程同步的目的是确保多个线程能够正确地访问和修改共享资源,避免数据竞争、死锁和资源竞争等问题。常用的线程同步技术包括互斥锁、信号量、条件变量、读写锁等。
C++11对于线程同步提供了四种机制,分别是
以两个线程对文件进行写入操作为例,如果不加以同步操作,由于线程时间片调度机制,会产生线程竞态,从而会导致写入的数据顺序发生混乱
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=50;
while(num--)
{
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
}
}
void myWriter2(int fd)
{
int num=50;
while(num--)
{
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
}
}
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
不同于C语言的互斥锁,C++的互斥锁定义完不需要初始化和销毁,直接使用即可
如下:对之前的多线程文件写入程序进行上锁,解决资源竞态的问题
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=50;
while(num--)
{
my_mutex.lock();
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
my_mutex.unlock();
}
}
void myWriter2(int fd)
{
int num=50;
while(num--)
{
my_mutex.lock();
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
my_mutex.unlock();
}
}
std::mutex my_mutex;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
如下图中,使用break跳出循环会跳过解锁,从而导致死锁问题
即使用lock_guard创建一个类模板对象,它会通过构造和析构的方式帮我们自动进行上锁解锁
lock_guard会在定义的时候上锁,出了它的当前作用域就会自动解锁(析构)
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=50;
while(num--)
{
lock_guardmy_lock(my_mutex);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
}
}
void myWriter2(int fd)
{
int num=50;
while(num--)
{
lock_guardmy_lock(my_mutex);
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
}
}
std::mutex my_mutex;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
unique_lock是一个类模板,使用等同于std::lock_guard
构造函数unique_lock的第二个参数
lock(),加锁:unique_lock创建的对象锁,可以在作用域内任意地方上锁,一般用于unlock()解锁后,再次上锁
unlock(),解锁:unique_lock创建的对象锁,可以在作用域内任意地方解锁,而不通过离开作用域析构解锁
try_lock(),尝试给互斥量加锁,如果拿不到锁,返回false,如果拿到了锁,返回true,这个函数是不阻塞的
总结:lock_guard不够灵活,它只能保证在析构的时候执行解锁操作;而unique_lock内部需要维护锁的状态,所以效率要比lock_guard低一点,在lock_guard能解决问题的时候,就用lock_guard
实例1:多线程文件写入
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=50;
while(num--)
{
unique_lockmy_lock(my_mutex);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
my_mutex.unlock();
}
}
void myWriter2(int fd)
{
int num=50;
while(num--)
{
unique_lockmy_lock(my_mutex);
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
}
}
std::mutex my_mutex;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
实例2:使用adopt_lock时,unique_lock定义时将不会自动上锁,需要我们用lock()方法指定地方进行上锁,否则无法上锁导致数据混乱
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=100;
while(num--)
{
//my_mutex.lock();
unique_lockmy_lock(my_mutex,std::adopt_lock);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
my_mutex.unlock();
}
}
void myWriter2(int fd)
{
int num=100;
while(num--)
{
unique_lockmy_lock(my_mutex);
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
}
}
std::mutex my_mutex;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
功能:一次锁住两个或者两个以上的互斥量(至少两个,多了不限),解决锁的顺序问题导致死锁的风险
解锁
举例:我们在创建多个锁的时候,上锁和解锁的顺序一定要相同,否则会导致死锁的问题
比如在下面程序中,线程1在解锁完锁2的时候,线程2会上锁2,这样就会导致线程1中无法对锁1解锁,从而在线程2中对锁1也无法上锁,这样就导致死锁,两个线程同时发生阻塞
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=100;
while(num--)
{
my_mutex1.lock();
my_mutex2.lock();
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
my_mutex2.unlock();
my_mutex1.unlock();
}
}
void myWriter2(int fd)
{
int num=100;
while(num--)
{
my_mutex2.lock();
my_mutex1.lock();
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
my_mutex1.unlock();
my_mutex2.unlock();
}
}
std::mutex my_mutex1,my_mutex2;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
但我们使用类模板提供的lock()方法,就不用关心上锁和解锁顺序问题,解锁先后与上锁无关
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num=100;
while(num--)
{
lock(my_mutex1,my_mutex2);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
my_mutex2.unlock();
my_mutex1.unlock();
}
}
void myWriter2(int fd)
{
int num=100;
while(num--)
{
lock(my_mutex1,my_mutex2);
write(fd,"nan",3);
write(fd,"jing",4);
write(fd,"\n",1);
my_mutex1.unlock();
my_mutex2.unlock();
}
}
std::mutex my_mutex1,my_mutex2;
};
int main(int argc, char const *argv[])
{
int fd = open("file",O_CREAT | O_WRONLY,0655);
if(fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1,&f,fd);
thread t2(&FileWriter::myWriter2,&f,fd);
t1.join();
t2.join();
return 0;
}
std::lock_guard的std::adopt_lock参数确保在构造时不会再次锁定互斥锁,而是假设这些互斥锁已经被锁定,并在析构时自动解锁
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class FileWriter
{
public:
void myWriter1(int fd)
{
int num = 100;
while (num--)
{
std::lock(my_mutex1, my_mutex2);
std::lock_guard lock1(my_mutex1, std::adopt_lock);
std::lock_guard lock2(my_mutex2, std::adopt_lock);
write(fd, "hello", 5);
write(fd, "world", 5);
write(fd, "\n", 1);
}
}
void myWriter2(int fd)
{
int num = 100;
while (num--)
{
std::lock(my_mutex1, my_mutex2);
std::lock_guard lock1(my_mutex1, std::adopt_lock);
std::lock_guard lock2(my_mutex2, std::adopt_lock);
write(fd, "nan", 3);
write(fd, "jing", 4);
write(fd, "\n", 1);
}
}
std::mutex my_mutex1, my_mutex2;
};
int main(int argc, char const *argv[])
{
int fd = open("file", O_CREAT | O_WRONLY, 0655);
if (fd == -1)
{
perror("open file error!");
exit(-1);
}
FileWriter f;
thread t1(&FileWriter::myWriter1, &f, fd);
thread t2(&FileWriter::myWriter2, &f, fd);
t1.join();
t2.join();
return 0;
}
互斥锁的最大问题是频繁的上锁解锁造成的开销比较大
std::call_once()功能是能够保证函数function只被调用一次,具备互斥量这种能力,而且效率上比互斥量消耗的资源更小
#include
#include
#include
#include
#include
#include
using namespace std;
std::mutex resource_mutex;
class MyCAS
{
private:
MyCAS(){}//私有化了的构造函数
private:
static MyCAS *m_instance;//静态成员变量
public:
static MyCAS *GetInstance()
{
//提高效率
//a)如果if(m_instance != NULL)条件成立,则表示肯定m_instance已经被new过了
//b)如果if(m_instance == NULL),不代表m_instance一定没被new过
if(m_instance == NULL)//双重锁定(双重检查)
{
std::unique_lockmymutex(resource_mutex);
if(m_instance == NULL)
{
m_instance == new MyCAS();
static CGarhuishou cl;
}
}
return m_instance;
}
class CGarhuishou//类中套类,用来释放对象
{
public:
~CGarhuishou()//类的析构函数中
{
if(MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func()
{
cout << "测试"<
#include
#include
#include
#include
#include
#include
using namespace std;
std::mutex resource_mutex;
std::once_flag g_flag; //这是一个系统定义的标记
class MyCAS
{
static void CreateInstance()//只被调用一次
{
std::chrono::microseconds dura(20000);
std::this_thread::sleep_for(dura);
m_instance == new MyCAS();
static CGarhuishou cl;
}
private:
MyCAS(){}//私有化了的构造函数
private:
static MyCAS *m_instance;//静态成员变量
public:
static MyCAS *GetInstance()
{
//两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CrateInstance(),这里可以把g_flag看做一把锁
std::call_once(g_flag,CreateInstance);
cout << "call_once()执行完毕"<
前提:需要有互斥锁的支持
等待条件:wait(mutex,lambda)
唤醒:notify_once()只能通知一个线程
唤醒:notify_all()通知所有线程
打印ABC
#include
#include
#include
#include
#include
using namespace std;
class Print
{
public:
void printA()
{
while(1)
{
//lock_guardmy_lock(my_mutex);
unique_lockmy_lock(my_mutex);
my_cond.wait(my_lock,[this](){
if(count==0)
{
return true;
}
return false;
});
cout << "A" <my_lock(my_mutex);
unique_lockmy_lock(my_mutex);
my_cond.wait(my_lock,[this](){
if(count==1)
{
return true;
}
return false;
});
cout << "B" <my_lock(my_mutex);
unique_lockmy_lock(my_mutex);
my_cond.wait(my_lock,[this](){
if(count==2)
{
return true;
}
return false;
});
cout << "C" <
原子操作:是在多线程中不会被打断的程序片段(汇编指令);原子操作比互斥量更胜一筹;
作用:原子操作一般用来保护单一变量,不保护代码段
std::atomic
我们也可以直接读写原子变量的值,但是使用提供的成员方法更安全
atomicatm;
atm=0;
cout<
按理说两个线程都加一百万次,结果应该是二百万,但实际的结果却不是。因为++实际上是两个操作,先加1,然后给变量赋值,因此并不是原子操作,所以会导致两个线程竞态,导致结果变小。
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
//std::atomicg_mycont(0);
int g_mycont = 0;
void mythread()
{
for(int i =0 ;i<1000000;i++)
{
g_mycont++;//对应的操作是个原子操作(不会被打断)
//g_mycont += 1;
//g_mycont = g_mycont + 1; //结果不对,不是原子操作
}
}
int main()
{
thread myobj1(mythread);
thread myobj2(mythread);
myobj1.join();
myobj2.join();
cout << "两个线程执行完毕,最终的g_mycont的结果是:"<
通过加锁可以解决问题
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
//std::atomicg_mycont(0);
int g_mycont = 0;
mutex my_mutex;
void mythread()
{
for(int i =0 ;i<1000000;i++)
{
my_mutex.lock();
g_mycont++;//对应的操作是个原子操作(不会被打断)
//g_mycont += 1;
//g_mycont = g_mycont + 1; //结果不对,不是原子操作
my_mutex.unlock();
}
}
int main()
{
thread myobj1(mythread);
thread myobj2(mythread);
myobj1.join();
myobj2.join();
cout << "两个线程执行完毕,最终的g_mycont的结果是:"<
C++11支持将变量申明为原子值
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
std::atomicg_mycont(0);
void mythread()
{
for(int i =0 ;i<1000000;i++)
{
g_mycont++;//对应的操作是个原子操作(不会被打断)
//g_mycont += 1;
//g_mycont = g_mycont + 1; //结果不对,不是原子操作
}
}
int main()
{
thread myobj1(mythread);
thread myobj2(mythread);
myobj1.join();
myobj2.join();
cout << "两个线程执行完毕,最终的g_mycont的结果是:"<
注:atomic原子操作仅对于++、--、&=、|=是支持的
强顺序保证了程序的正确性,但效率会很低,一般编译器优化的时候都是按照弱顺序来执行