https://legacy.cplusplus.com/reference/thread/thread/
可以传一个对象过去,后面的是该对象的参数。 可以创建一个空线程,创建之后什么都不做,后面可以对这个空线程再次进行操作。 不支持拷贝构造,但是可以移动构造。
这个接口是获取当前线程id的,因为对象调用不方便,所以就封装到了命名空间当中。
https://legacy.cplusplus.com/reference/thread/this_thread/ 第二个是将本线程的时间片让给其他线程。 第三个可以设置休眠到某个时间点。 第四个是休眠时间段是多少。
#include<iostream>
#include<thread>
using namespace std;
int sum = 0;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
sum++;
}
int main()
{
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << sum << std::endl;
return 0;
}
因为++和- - 操作是三条汇编指令,中途有可能因为时间片的到来而切换,导致线程安全问题,C++98的解决方式是用锁来进行问题的处理,但是锁会大幅度的影响性能,并且控制不好会造成死锁问题。 因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入 的原子操作类型,使得线程间数据的同步变得非常高效。 也是CAS操作: https://baike.baidu.com/item/CAS/7371138 底层其实是,对于两个线程的++和- - ,先拿到值进行计算,结果放在寄存器当中,然后与被++或- - 的变量进行对比,如果相同就保留当前结果,然后重新进行当前的++或 - - 操作。 也就是说每次++要不就是成功,要不就是失败,重新进行++。
#include<iostream>
#include<thread>
#include<atomic>
using namespace std;
atomic<int>sum = 0;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
sum++;
}
int main()
{
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << sum << std::endl;
return 0;
}
这个是判断当前线程有没有锁。
if(try_lock())
{
//如果获取到锁就走这里
}
else
{
//如果锁被其他线程拿走了就走这里
}
这个是递归互斥锁,防止了死锁的问题。 底层原理类似判断想获取锁的线程是不是拿到锁的线程,如果是直接进去即可。
这个是RAII操作,出了作用域自动释放锁。
与lock_gard类似。
上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock。 修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)。 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相同)、mutex(返回当前unique_lock所管理的互斥量的指针)。
这是第一种写法:
#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
int main()
{
int i = 0;
thread t1([&]() {
while (i < 100)
{
if (i % 2)
{
cout << this_thread::get_id() << ":" << i << endl;
i++;
}
}
});
thread t2([&]() {
while (i <= 100)//这里是为了防止++操作的非原子性导致最终结果可能没有100
{
if (i % 2 == 0)
{
cout << this_thread::get_id() << ":" << i << endl;
i++;
}
}
});
t1.join();
t2.join();
return 0;
}
但是当前的程序每次只能有一个线程可以进入if条件中,也就是说每次都需要CPU去判断另一个不满足条件的线程,如果数值大的话会很浪费CPU的资源。
https://legacy.cplusplus.com/reference/condition_variable/condition_variable/
这些接口是通知和接收的接口。
#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
using namespace std;
int main()
{
condition_variable cv;
mutex tex;
int i = 0;
bool flag = false;
//奇数
thread t1([&]() {
while (i < 100)
{
unique_lock<mutex> lock(tex);//加锁
while (flag == true)
cv.wait(lock);//阻塞
cout << this_thread::get_id() << ":" << i << endl;
i++;
flag = true;
cv.notify_one();//唤醒wait
}
});
//偶数
thread t2([&]() {
while (i < 100)
{
unique_lock<mutex> lock(tex);//加锁
while(flag == false)
cv.wait(lock);//阻塞
cout << this_thread::get_id() << ":" << i << endl;
i++;
flag = false;
cv.notify_one();//唤醒wait
}
});
t1.join();
t2.join();
return 0;
}
这是针对两个线程打印奇数偶数的改良,这样CPU占用率就不会太高了。
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数 据( 其单位可以是bit,byte,packet )的抽象描述。 C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设 备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。 它的特性是:有序连续、具有方向性。 为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功 能。
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
,_b(2)
{}
operator int()
{
return _a + _b;
}
private:
int _a;
int _b;
};
int main()
{
A a(1);
int b = a;//自定义类型隐式转换成内置类型
cout << b << endl;
return 0;
}
C++根据文件内容的数据格式分为二进制文件和文本文件。采用文件流对象操作文件的一般步 骤:
这里也不需要自己关闭文件,是RAII的。
#include<iostream>
#include<fstream>
using namespace std;
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
struct ServerInfo
{
char _address[32];
int _port;
Date _date;
};
struct ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename, ios_base::out | ios_base::binary);
ofs.write((const char*)&info, sizeof(info));
}
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename, ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(info));
}void WriteText(const ServerInfo& info)
{
ofstream ofs(_filename);
ofs << info._address << " " << info._port << " " << info._date;
}
void ReadText(ServerInfo& info)
{
ifstream ifs(_filename);
ifs >> info._address >> info._port >> info._date;
}
private:
string _filename; // 配置文件
};
int main()
{
ServerInfo winfo = { "192.0.0.1", 80, { 2022, 4, 10 } };
// 二进制读写
ConfigManager cf_bin("test.bin");
cf_bin.WriteBin(winfo);
ServerInfo rbinfo;
cf_bin.ReadBin(rbinfo);
cout << rbinfo._address << " " << rbinfo._port << " "<< rbinfo._date << endl;
// 文本读写
ConfigManager cf_text("test.text");
cf_text.WriteText(winfo);
ServerInfo rtinfo;
cf_text.ReadText(rtinfo);
cout << rtinfo._address << " " << rtinfo._port << " " <<rtinfo._date << endl;
return 0;
}
这里二进制读写的时候ServerInfo结构体里面不能将char _address[32]变成string类型,因为读写的时候读进去的是指针,容易造成野指针的问题,所以尽量不要用二进制读写。 文本读写那里就跟cout和cin一样,他们其实都是相同的作用,都可以将任意数据类型转成字符串类型,也可以进行重载。
标准库三个类: istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作。
#include<iostream>
#include<sstream>
using namespace std;
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
int main()
{
int i = 10;
double dl = 1.01;
Date d(2023, 6, 19);
ostringstream oss;
oss << i << " ";
oss << dl << " ";
oss << d << endl;
string str = oss.str();
cout << str << endl;
return 0;
}