转载:一些c++多线程习题 - geloutingyu - 博客园 (cnblogs.com)
知识点:
this_thread::yield(); //当前线程放弃执行,操作系统调度另一线程继续执行
ifstream in(fileName); //ifstream 是针对文件读取的流
ofstream out(fileName, ios::app); //ofstream 是针对文件写入的流
文件流对象有两个可用于随机文件访问的成员函数:tellp 和 tellg
tellp 用于返回写入位置,tellg 则用于返回读取位置
自己手写练习才有感觉:
第一题:子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码
方法一:采用this_thread::yield();
#include <thread>
#include <mutex>
using namespace std;
const int countNum = 50;
int flag = 10;
mutex mu;
void fun(int num,string_view str)
{
for (int i=0;i< countNum;++i)
{
while (num != flag)
{
this_thread::yield(); //当前线程放弃执行,操作系统调度另一线程继续执行
}
lock_guard<mutex> lock(mu);
for (int j=0;j<flag;++j)
{
cout << str << ":" << j << endl;
}
flag = (flag == 10 ? 100 : 10);
}
}
int main()
{
thread A(fun, 10, "child");
fun(100, "father");
A.join();
return 0;
}
总结:fun(100, "father");要放在join之间,子线程A先于函数执行前创建了,主要是要让主线程先跑起来,this_thread::yield();使当前线程停止,让另外线程启动。
方法二:采用条件变量condition_variable
#include <mutex>
#include <condition_variable>
using namespace std;
const int countNum = 50;
int flag = 10;
condition_variable cv;
mutex mu;
void fun(int num,string_view str)
{
for (int i=0;i< countNum;++i)
{
unique_lock<mutex> lock(mu);
cv.wait(lock, [&]{return num == flag; });
for (int j=0;j<flag;++j)
{
cout << str << ":" << j << endl;
}
flag = (flag == 10 ? 100 : 10);
cv.notify_one();//唤醒被锁住的线程
}
}
int main()
{
thread A(fun, 10, "child");
fun(100, "father");
A.join();
return 0;
}
总结:condition_variable 只能和unique_lock搭配使用,原因可以看C++11:为什么 std::condition_variable 使用 std::unique_lock? - IT工具网 (coder.work)。
第二题:编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推
方法一:采用this_thread::yield();
方法二:采用条件变量condition_variable
总结:注意cv.wait 中填写的条件是 如果为真则执行接下来的代码。
第三题:(google笔试题):有四个线程1、2、3、4。线程 1 的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:
A:1 2 3 4 1 2....
B:2 3 4 1 2 3....
C:3 4 1 2 3 4....
D:4 1 2 3 4 1....
#include <thread>
#include <mutex>
#include <condition_variable>
#include <fstream>
using namespace std;
mutex mu;
condition_variable cv;
const string fileName = "D://vsTest//BeStrong//txt//strong_";
ofstream f0(fileName+"A.txt");
ofstream f1(fileName + "B.txt");
ofstream f2(fileName + "C.txt");
ofstream f3(fileName + "D.txt");
ofstream* file[4];
int v[] = {4,1,2,3};
int a =0, b = 0, c = 0, d = 0;
void fun(int x,int num)
{
while (num--)
{
for (int i=0;i<4;i++)
{
unique_lock<mutex> lock(mu);
if (x==1)
{
cv.wait(lock, [&] { return v[i] == 4; });
(*file[i]) << 1;
v[i] = 1;
a++;
lock.unlock();
cv.notify_all();
}
else if (x == 2)
{
cv.wait(lock, [&] { return v[i] == 1; });
(*file[i]) << 2;
v[i] = 2;
b++;
lock.unlock();
cv.notify_all();
}
else if (x == 3)
{
cv.wait(lock, [&] { return v[i] == 2; });
(*file[i]) << 3;
v[i] = 3;
c++;
lock.unlock();
cv.notify_all();
}
else
{
cv.wait(lock, [&] { return v[i] == 3; });
(*file[i]) << 4;
v[i] = 4;
d++;
lock.unlock();
cv.notify_all();
}
}
}
}
int main()
{
file[0] = &f0;
file[1] = &f1;
file[2] = &f2;
file[3] = &f3;
thread t0(fun,1,10);
thread t1(fun, 2, 10);
thread t2(fun, 3, 10);
fun(4,10);
t0.join();
t1.join();
t2.join();
for (size_t i = 0; i < 4; i++)
{
file[i]->close();
}
return 0;
}
总结:我在每个线程中放了一个变量,原本以为每个变量应该是只差1,查看结果如图,说明线程并不是顺序执行的,即使用条件变量,这本来就是线程的特点,随机性。但结果是顺序。这就是多线程并发的优势。
第四题:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者读时写者也不能写
#include <thread>
#include <mutex>
#include <condition_variable>
#include <fstream>
using namespace std;
mutex mu;
condition_variable cv;
const string fileName = "D://vsTest//BeStrong//txt//strong_A.txt";
ifstream in(fileName); //ifstream 是针对文件读取的流
ofstream out(fileName, ios::app); //ofstream 是针对文件写入的流
int cnt = 0;
void write()
{
char ch;
while (true)
{
unique_lock<mutex> lock(mu);
ch = getchar();
out << ch;
++cnt;
lock.unlock();
cv.notify_all();
}
}
void read()
{
char ch;
while (true)
{
unique_lock<mutex> lock(mu);
cv.wait(lock, [&] { return cnt>0; });
in >> ch;
cout << "cout:" << ch << endl;
--cnt;
}
}
int main()
{
cnt = in.tellg();//文件流对象有两个可用于随机文件访问的成员函数:tellp 和 tellg
//tellp 用于返回写入位置,tellg 则用于返回读取位置
thread w(write);
thread r0(read);
thread r1(read);
thread r2(read);
w.join();
r0.join();
r1.join();
r2.join();
in.close();
out.close();
return 0;
}
第四题:STL 中的 queue 是非线程安全的,一个组合操作:front(); pop() 先读取队首元素然后删除队首元素,若是有多个线程执行这个组合操作的话,可能会发生执行序列交替执行,导致一些意想不到的行为。因此需要重新设计线程安全的 queue 的接口
#include <thread>
#include <mutex>
#include <condition_variable>
#include <fstream>
#include <queue>
using namespace std;
template<typename T>
class thread_safe_queue {
private:
mutex mu;
condition_variable cv;
queue<T> que;
public:
thread_safe_queue() = default;
~thread_safe_queue() = default;
thread_safe_queue(const queue<T>& q) :que(q) {}
thread_safe_queue(const thread_safe_queue& tsq){
std::unique_lock<mutex> lock(mu);
que = tsq;
}
void push(const T &data)
{
std::unique_lock<mutex> lock(mu);
que.push(data);
cout << "push:" << data << endl;
lock.unlock();
cv.notify_all();
}
T pop(void)
{
std::unique_lock<mutex> lock(mu);
cv.wait(lock, [&] {return !que.empty(); });
T value = que.front();
que.pop();
return value;
}
bool empty(void)
{
std::unique_lock<mutex> lock(mu);
return que.empty();
}
};
thread_safe_queue<int> q;
mutex mu;
int main()
{
auto push_value = [&] {
for (int i=0;i<100;++i)
{
q.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
//this_thread::yield(); //降低cpu使用率,死循环cpu占用很高的
}
};
auto pop_value = [&] {
while (true)
{
while (!q.empty())
{
std::unique_lock<mutex> lock(mu);
cout <<"pop:"<< q.pop() << endl;
}
}
};
thread pushT(push_value);
thread popT0(pop_value);
thread popT1(pop_value);
pushT.join();
popT0.join();
popT1.join();
return 0;
}
总结:
通常来说使用sleep。
std::this_thread::sleep_for(std::chrono::milliseconds(1));
this_thread::yield(); //降低cpu使用率,死循环cpu占用很高的
两种效果不一样: