第一章:线程概念
#include <iostream>
#include <thread>
using namespace std;
int main()
{
//并发,进程,线程的基本概念和概述
//并发,进程,线程,必须掌握
// 1.并发
// 两个或者更多的任务(独立的活动)同时执行:一个程序同时执行多个独立任务
// 计算机单核CPU 同一时刻只能执行一个任务:由操作系统调度,每秒钟进行多次的任务切换,时间片轮转。
//并发假象(不是真正的并发);这种切换也叫做上下文切换,切换是有时间开销的,操作系统要保存你切换时的状态,执行进度等信息。
// 一会儿切换回来要复原
// 硬件发展,出现了多处理器计算机,用于服务器和高性能计算领域
// 台式机:在一块芯片上有多核cpu:双核 4核,8核
// 能够实现真正的并行执行多个任务(硬件开发),任务数量小于CPU核数
//使用并发的原因:主要是同时干多个任务,提高性能
// 2.可执行程序
// 扩展名为.exe为可执行程序, Linux中 有X的就是可执行程序
// 3.进程概念
// Windows下双击.exe运行可执行, Linux下。/a
// 可执行程序运行就是创建了进程。
// 4,线程概念
// 每个进程(执行起来的可执行程序),都有一个主线程,主线程是唯一的,一个进程只有一个主线程。
// 当执行可执行程序,产生进程后,主线程会随着进程自动启动。
// 运行程序,实际上是进程的主线程来执行。
//线程:用来执行代码,代码的执行道路。
//除了主线程外,可以自己写代码创建其他线程,走别的道路,去不同地方
//每创建一个新线程,就可以在同一时刻,多干一个不同的事。
//多线程(并发)不是越多越好,需要独立的堆空间(1M),线程切换要保存中间状态,
//切换会耗费本该属于程序运行的时间。
//总结线程:
//线程是用来执行代码的
//线程理解为代码的执行通路,一个新线程代表一条新的通路
//创建的线程不建议超过200-300个,
// 5学习心得
// 开发多线程程序:实力的体现。一个是商用的必须需求
//线程开发有一定的难度,实现代码复杂,理解上难一点。
// C++线程会涉及很对概念,不用基于求成。
//二、并发方法
// 两个或者更多的任务(独立的活动)同时执行:一个程序同时执行多个独立任务
// 实现并发:通过多个进程。
//在单独的进程中,创建多个线程。自己写代码
//1.多进程并发
// 程序启动就是进程,
// 进程通信(同一个电脑:文件,管道,消息队列,共享内存)
// 不同电脑:SOCKET通信技术。
//2.多线程并发:单个进程创建的多个线程。
/*
// 线程:每个线程都有自己独立的运行路径,但是一个进程中的所有线程共享地址空间。(共享内存)
// 全局变量,指针,引用,都可以在线程中传递,使用多线程开销小于多进程。
共享内存带来的问题,数据一致性的问题。
//多进程并发和多线程可以混合使用,优先使用多线程手段。
本章只讲多线程并发技术。
和进程比,线程启动速度更快,更轻量级,开销更少,执行速度更快,
缺点:使用有难度,要小心处理,数据一致性问题。
//三。C++11新标准库
以前:windowns:CreateThread(),_beginthred()
linux:phread_create();
//临界区,互斥量。
POSIX:跨平台,但是需要配置,用起来也不方便。
从C++11引入了多线程支持,意味着可移植(跨平台)。大大减少工作量
*/
return 0;
}
第二章 :线程创建,启动
函数创建线程
#include <iostream>
#include <thread>
using namespace std;
void myprint()
{
cout << "我的线程开始执行" << endl;
cout << "我的线程执行完毕" << endl;
}
int main()
{
/*
//线程运行的开始和结束。
//主线程从main()开始执行,我们从另一个函数开始执行。
//当主线程执行完毕后,整个进程就执行完毕,如果子线程没有执行完毕,会强行终止
//要让子线程报仇运行状态,让主线程一直保存运行。
有例外
a.包含 #include <thread>
b.创建的初始函数 myprint
*/
thread t(myprint);
t.join();
cout << "I Love China" << endl;
return 0;
}
类创建线程
#include <iostream>
#include <thread>
using namespace std;
void myprint()
{
cout << "我的线程开始执行" << endl;
}
class TA {
public:
int m;
TA(int i)
: m(i) {};
~TA() { cout << "析构函数调用了" << endl; };
TA(const TA& ta)
: m(ta.m)
{
cout << "拷贝构造函数调用了" << endl;
};
void operator()()
{
cout << "我的线程operator()开始执行了" << endl;
cout << "m1的值是 = " << m << endl; //,因为程序可能已经销毁了,会产生不可预料的代码。
cout << "我的线程operator()结束执行了" << endl;
}
};
int main()
{
/*
//线程运行的开始和结束。
//主线程从main()开始执行,我们从另一个函数开始执行。
//当主线程执行完毕后,整个进程就执行完毕,如果子线程没有执行完毕,会强行终止
//要让子线程报仇运行状态,让主线程一直保存运行。
有例外
a.包含 #include <thread>
b.创建的初始函数 myprint
有两个线程在执行,可以同时做两个事情,一条线被堵住了,另一条线可以执行。
(1.1)thread 是类,用来创建线程
(1.2)join 加入/汇合,阻塞主线程,让主线程等待子线程执行完毕,主线程在继续执行。
如果主线程执行完毕,但子线程还没有执行完毕,写出来的代码是不稳定的,
需要主线程等待子线程执行完毕在退出。
(1.3)detach()分离 :传统需要等待子线程执行完毕,然后退出,
创建了多个子线程,让主线程等待子线程结束,在退出
一旦detach后,会被运行时库接管,,清理程序资源,(守护线程deamon)
(1.4)jsonable()返回是否可以join或者detach
*/
// 可调用对象
// thread t(myprint); //创建了线程已经开始执行了。
// cout<<t.joinable()<<endl; //返回是否可以join和detach
// t.join();
// t.detach(); //一旦调用detach ,就不能再用join了,否则会有异常。
// cout << "主线程收尾,最终安全退出" << endl;
//二、其他创建线程的方式
int myi = 9;
TA a(myi);
thread t(a); // a是可调用对象
t.detach();
// t.join();
cout << "主线程收尾,最终安全退出" << endl;
return 0;
}
lambda创建线程
// 三。用lambda表达式
auto mylambda = []() {
cout << "我的线程3开始执行了" << endl;
cout << "我的线程3执行结束了" << endl;
};
thread t(mylambda);
cout << "主线程收尾,最终安全退出" << endl;
第三章:线程传参传参
#include <iostream>
#include <thread>
using namespace std;
void myprint(const int i, const string& printf) //此处i不是真的引用,他和外部地址不一样,虽然安全,但是不建议使用,此处使用值传递,
// void myprint(const int& i, char* printf) //此处i不是真的引用,他和外部地址不一样,虽然安全,但是不建议使用,此处使用值传递,
{
cout << "i = " << i << "i address = " << &i << endl;
cout << "printf = " << printf << "printf address = " << &printf << endl;
}
void test(int& i, char* printf)
{
cout << "i = " << i << "i address = " << &i << endl;
cout << "printf = " << printf << "printf address = " << printf << endl;
}
int main()
{
/*
//一、传递临时对象作为线程参数
(1.1)要避免的陷阱(解释1) 相对安全
(1.2)要避免的陷阱(解释2)
*/
int muar = 1;
int& m = muar;
char array[] = "afda";
cout << "m address = " << &m << endl;
cout << "array address = " << &array << endl;
// thread t(myprint, m, array); //但是array是什么时候转换为string的
//事实上,array都被回收了(main函数执行完了),系统才用array转string的可能
thread t(myprint,m,string(array)); //最终写法,这是一个可以保证在线程中用肯定有效的写法。
t.detach();
// test(m, array);
cout << "Finish!!!" << endl;
return 0;
}
#include <iostream>
#include <thread>
using namespace std;
void myprint(int i, const string& printf) //此处i不是真的引用,他和外部地址不一样,虽然安全,但是不建议使用,此处使用值传递,
// void myprint(const int& i, char* printf) //此处i不是真的引用,他和外部地址不一样,虽然安全,但是不建议使用,此处使用值传递,
{
i = 100;
cout << "i = " << i << "i address = " << &i << endl;
cout << "printf = " << printf << "printf address = " << &printf << endl;
}
class A {
public:
int m_i;
A(int a)
: m_i(a)
{
// m_i = a;
cout << "构造函数执行" << endl;
};
A(const A& a)
: m_i(a.m_i)
{
// m_i = a.m_i;
cout << "拷贝构造函数执行";
};
~A() { cout << "析构函数执行" << endl; };
};
void test(const int i, const A& a)
{
cout << &a << endl;
return;
}
int main()
{
/*
//一、传递临时对象作为线程参数
(1.1)要避免的陷阱(解释1) 相对安全
(1.2)要避免的陷阱(解释2)
(1.3总结)
如果是简单的类型用值传递,如果传递类对象,避免隐式类型转换。全部在创建线程这一行就构建出对象来,在函数中,用引用接受。
最终结论()
建议不使用detach,就不会存在对内存的非法引用问题。
*/
int muar = 1;
// int& m = muar;
int m2 = 2;
thread t(test, muar, A(m2)); //类对象是最后调用的,隐式转为类,不会构造类,有问题。
// 使用显示转换会先进行转换。通过调用临时构造函数,在调用拷贝构造函数。
//在创建线程的同时创建临时对象的方法传递参数可行。
t.detach();
cout << "Finish!!!" << endl;
return 0;
}
第四章:创建多个线程
创建多个线程
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
void myThread(int num)
{
cout << num << "myprint线程开始执行" << endl;
cout << num << "myprint线程结束执行" << endl;
}
int main()
{
//一.创建多个线程
vector<thread> mythreads;
for (int i = 0; i < 10; i++) {
mythreads.push_back(thread(myThread, i)); //创建十个线程,并运行
}
for (auto iter = mythreads.begin(); iter != mythreads.end(); iter++) {
iter->join(); //等待结果返回
}
cout << "finish " << endl;
return 0;
}
数据共享问题
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
vector<int> v = { 1, 2, 3 };
void myThread(int num)
{
cout << num << "myprint线程开始执行" << endl;
cout << this_thread::get_id() << "线程打印v的值" << v[0] << " " << v[1] << endl;
cout << num << "myprint线程结束执行" << endl;
}
class A {
public:
//把收到的消息传入队列
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i) {
cout << "inMsgRecvQueue()<<插入数据" << i << endl;
msgRecvQueue.push_back(i); //设置数字为收到的命令,
}
}
//把数据读取出来
void outRecvQueye()
{
for (int i = 0; i < 10000; ++i) {
if (!msgRecvQueue.empty()) {
//不为空
int command = msgRecvQueue.front(); //从前开始取,不检查元素是否存在
msgRecvQueue.pop_front(); //移除,但不返回
//考虑处理
} else {
cout << "outRecvQueye()执行,但是元素为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;
};
int main()
{
// (2.3)其他案例
// 数据共享;火车买票
//三.共享数据的保护代码
//网络服务器,两个线程,一个收集命令,写入队列,另一个取出信息.
// list容器,频繁的按顺序插入和删除数据时效率高,vector容器随机的插入和删除数据效率高
//用成员函数作为线程参数写线程
A obj;
thread recv(&A::inMsgRecvQueue, &obj);
thread t(&A::outRecvQueye, &obj); //保证是同一个对象,保证线程用的是同一个对象
t.join();
return 0;
}
数据共享问题解决
互斥量(mutex)|| std::lock_guard
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
class A {
public:
//把收到的消息传入队列
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i) {
cout << "inMsgRecvQueue()<<插入数据" << i << endl;
std::lock_guard<mutex> sbguard(mu); //使用lock_guard
mu.lock();
msgRecvQueue.push_back(i); //设置数字为收到的命令,
mu.unlock();
}
}
//把数据读取出来
void outRecvQueye()
{
for (int i = 0; i < 100000; ++i) {
if (!msgRecvQueue.empty()) {
//不为空
int command = msgRecvQueue.front(); //从前开始取,不检查元素是否存在
msgRecvQueue.pop_front(); //移除,但不返回
//考虑处理
cout << "读取出来的数据是:" << command << endl;
} else {
cout << "outRecvQueye()执行,但是元素为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;
mutex mu; //创建了互斥量
};
int main()
{
// (2.3)其他案例
// 数据共享;火车买票
//三.共享数据的保护代码
//网络服务器,两个线程,一个收集命令,写入队列,另一个取出信息.
// list容器,频繁的按顺序插入和删除数据时效率高,vector容器随机的插入和删除数据效率高
//用成员函数作为线程参数写线程
A obj;
thread out_t(&A::outRecvQueye, &obj); //保证是同一个对象,保证线程用的是同一个对象
thread recv_t(&A::inMsgRecvQueue, &obj);
recv_t.join();
out_t.join();
//互斥量,保护共享数据mutex 多个线程尝试,只有一个可以成功
//步骤:先lock操作共享数据,在unlock.两者需要成对使用.
//为了防止大家出现忘记写unlock的情况,引入了一个std::lock_guard的类模板,替你unlock
// lock_guard直接取代lock 和unlock,也就是说用了lock_guard再也不能用lock
return 0;
}
死锁
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
class A {
public:
//把收到的消息传入队列
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i) {
cout << "inMsgRecvQueue()<<插入数据" << i << endl;
// std::lock_guard<mutex> sbguard(mu); //使用lock_guard
// mu.lock();
// mu.lock(); //再次加锁,出现死锁现象.
mu.try_lock();
mu.try_lock(); //可以使用try_locy ,如果枷锁则返会false
msgRecvQueue.push_back(i); //设置数字为收到的命令,
mu.unlock();
// mu.unlock();
}
}
//把数据读取出来
void outRecvQueye()
{
for (int i = 0; i < 100; ++i) {
if (!msgRecvQueue.empty()) {
//不为空
int command = msgRecvQueue.front(); //从前开始取,不检查元素是否存在
msgRecvQueue.pop_front(); //移除,但不返回
//考虑处理
cout << "读取出来的数据是:" << command << endl;
} else {
cout << "outRecvQueye()执行,但是元素为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;
mutex mu; //创建了互斥量
};
int main()
{
// (2.3)其他案例
// 数据共享;火车买票
//三.共享数据的保护代码
//网络服务器,两个线程,一个收集命令,写入队列,另一个取出信息.
// list容器,频繁的按顺序插入和删除数据时效率高,vector容器随机的插入和删除数据效率高
//用成员函数作为线程参数写线程
A obj;
thread out_t(&A::outRecvQueye, &obj); //保证是同一个对象,保证线程用的是同一个对象
thread recv_t(&A::inMsgRecvQueue, &obj);
recv_t.join();
out_t.join();
//互斥量,保护共享数据mutex 多个线程尝试,只有一个可以成功
//步骤:先lock操作共享数据,在unlock.两者需要成对使用.
//为了防止大家出现忘记写unlock的情况,引入了一个std::lock_guard的类模板,替你unlock
// lock_guard直接取代lock 和unlock,也就是说用了lock_guard再也不能用lock
//死锁,当你有两把锁的时候,先锁后
return 0;
}
以下内容为旧的
使用线程第一个代码
#include <iostream>
#include <thread>
using namespace std;
void ThreadMain()
{
cout << "thread main id = " << this_thread::get_id() << endl;
for (int i = 0; i < 11; i++) {
this_thread::sleep_for(10ms); // 10 毫秒
// this_thread::sleep_for(chrono::seconds(1)); // 1秒
} //设置睡眠时间
cout << "end thread id = " << this_thread::get_id() << endl;
}
int main()
{
thread t(ThreadMain); //线程创建启动
t.join(); //堵塞等待子线程退出
//通过id 区分线程号
cout << " main id = " << this_thread::get_id() << endl;
// cout << "thread main id = " << t.get_id() << endl;
return 0;
}
线程分离
#include <iostream>
#include <thread>
using namespace std;
bool is_exis = false;
void ThreadMain()
{
cout << "thread main id = " << this_thread::get_id() << endl;
for (int i = 0; i < 11; i++) {
if (!is_exis)
break;
// this_thread::sleep_for(10ms); // 10 毫秒
this_thread::sleep_for(chrono::seconds(1)); // 1秒
} //设置睡眠时间
cout << "end thread id = " << this_thread::get_id() << endl;
}
int main()
{
{
thread t(ThreadMain); //出错,thread对象被销毁,子线程还在运行
t.detach(); //子线程与主线程分离 守护线程
//坑:主线程退出后,子线程还在运行
}
thread th(ThreadMain);
this_thread::sleep_for(chrono::seconds(1));
is_exis = true;
th.join();
return 0;
}
线程创建的多种方式
传参
#include <iostream>
#include <thread>
using namespace std;
class Person {
public:
int a = 10;
Person() { cout << "creaet " << endl; }
Person(const Person& p)
{
cout << "copy " << endl;
this->a = p.a;
};
~Person() { cout << "delete " << endl; }
};
void ThreadMain(int p1, float p2, string str, Person s)
{
this_thread::sleep_for(100ms);
cout << "p1 = " << p1 << endl;
cout << "p2 = " << p2 << endl;
cout << "str = " << str << endl;
cout << "Person =" << s.a << endl;
}
int main()
{
Person s;
//所有的参数是复制传参
thread th(ThreadMain, 101, 23.4, "hello", s); //参时后面是... 代表可以传递任意的参数。
th.join(); //等待
cout << "finish iA = " << s.a << endl;
return 0;
}
线程安全问题
当我们用多个线程同时操作一个全局数据时,会发生线程安全,导致值不是我们预期的,可以通过互斥锁,读写锁,原子操作,条件变量,线程本地变量
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
int sharedData = 0;
// std::mutex mutex1;
void incrementData()
{
for (int i = 0; i < 100000; ++i) {
sharedData++; // 竞态条件,多个线程同时对 sharedData 执行写操作
}
}
int main()
{
std::thread t1(incrementData);
std::thread t2(incrementData);
t1.join();
t2.join();
std::cout << "Final value of sharedData: " << sharedData << std::endl;
return 0;
}
互斥锁
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
int sharedData = 0;
std::mutex mutex1;
void incrementData()
{
for (int i = 0; i < 100000; ++i) {
mutex1.lock();
sharedData++; // 竞态条件,多个线程同时对 sharedData 执行写操作
mutex1.unlock();
}
}
int main()
{
std::thread t1(incrementData);
std::thread t2(incrementData);
t1.join();
t2.join();
std::cout << "Final value of sharedData: " << sharedData << std::endl;
return 0;
}
原子操作
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
// int sharedData = 0;
std::atomic<int> sharedData = 0; //创建原子类
//atomic_int sharedData = 0; //创建原子类 使用内置的宏声明
using namespace std;
void incrementData()
{
for (int i = 0; i < 100000; ++i) {
sharedData++; // 竞态条件,多个线程同时对 sharedData 执行写操作
}
}
int main()
{
clock_t start = clock(); //获取开始时间
std::thread t1(incrementData);
std::thread t2(incrementData);
t1.join();
t2.join();
std::cout << "Final value of sharedData: " << sharedData << std::endl;
clock_t end = clock();
cout << end - start << endl;
return 0;
}
线程本地变量
#include <iostream>
#include <thread>
thread_local int g_counter = 0; // 线程本地变量
void incrementCounter()
{
++g_counter;
std::cout << std::this_thread::get_id() << ": " << g_counter << std::endl;
}
int main()
{
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
return 0;
}