C/C++程序员最苦恼的是自己跨平台能力不是一半弱。如果想跨平台,有一大波函数库等着你来深入研究。你再反观java。。。。
所谓原子操作,就是多线程中“最小的且不可并行化的操作”。通常原子操作都是互斥访问保证的。但是互斥一般靠平台相关汇编指令,这也是为什么C++11之前一直没有做的原因。
#include //原子操作需要的头文件
#include //线程头文件
#include
using namespace std;
atomic_llong total{ 0 };//原子数据类型long long
//这样的构词法还有atomic_int等等
//下面的东西不是这一节的内容
void func(int)
{
for (long long i = 0; i < 100000000LL; ++i)
total = total + i;
}
int main()
{
thread t1(func, 0);
thread t2(func, 0);
t1.join();
t2.join();
cout << total << endl;
return 0;
}
可以通过atomic
原子操作通常属于“资源型”的数据。这意味着多个线程通常只能访问单个原子类型的拷贝。因此C++11中,原子类型只能从其模板参数中进行构造,标准不允许原子类型进行拷贝构造,移动构造,以及使用operator=等 ,以防止出现意外。这样无法编译。
atomic
为了避免线程间关于a的竞争。模板改了很多地方。。
atomic
int b =a;//相当于b = a.load();
int a =1;//相当于a.store(1);
#include
#include
using namespace std;
void func(int)
{}
int main()
{
thread t1(func, 0);
t1.join();
return 0;
}
线程的构造函数参数,可以视为,(要执行的函数名,该函数参数1,该函数参数2,。。。。)
#include
#include
using namespace std;
void func(int a)
{
for (int i = 0; i < 10; ++i)
std::cout << a << endl;
}
int main()
{
int a;//停机变量无意义
{
thread t1(func, 1);
thread t2(func, 2);
}//运行到这里,t1,t1没有了
std::cin >> a;
return 0;
}
如果改成这样,可以正常执行:
int main()
{
int a;
{
thread t1(func, 1);
thread t2(func, 2);
t1.join();//主线程被阻塞了,停在这里,等t1线程对象的线程执行完再运行
t2.join();
}
std::cin >> a;
return 0;
}
如果不希望线程被阻塞吗,将线程与线程对象分离可以用t1.detach();将线程与线程对象分离。这里要说明下,线程与线程对象是两码事(这个很重要)。我们仅是依托线程对象来创建线程。
如下;
int main()
{
int a;
{
thread t1(func, 1);
thread t2(func, 2);
t1.detach();//主线程没有被阻塞,将线程与线程对象分离
t2.detach();
}//运行到这一步,线程对象依然会析构,但是线程却可以继续运行。
std::cin >> a;
return 0;
}
这也从侧面描述了,用detach将线程与线程对象分开后,就不能合并了。
线程不能复制,但可以移动(即使用std::move())。线程移动后,线程对象t不再代表任何线程。。
另外还可以用std::bind或lambda表达式创建。
thread t1(std::bind(func, 1));
thread t2([](int, int) {},1,2);
t1.join();
t2.join();
互斥量是一种同步原语,线程同步手段,用于保护多线程同时访问共享数据。“互斥量”的翻译十分有迷惑性。它就是“锁类”。以至于如果不这样理解,将会对后面的条件变量混淆。C++11提供了4种互斥量。
互斥量接口都很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量所有权为止。线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现。try_lock()尝试锁定互斥量,如成功返回true如失败返回false,他是非阻塞的,看来可以用来检查当前互斥量的状态。
改动上面的程序将函数变成加锁的:
#include
#include
#include
using namespace std;
std::mutex uni_lock;
void func(int a)
{
uni_lock.lock();
for (int i = 0; i < 10; ++i)
std::cout << a << endl;
uni_lock.unlock();
}
int main()
{
int a;
{
thread t1(func, 1);
thread t2(func, 2);
t1.join();
t2.join();
}
std::cin >> a;
return 0;
}
这显示这就友好了
官方建议尽量使用更安全的lock_guard。因为他在构造时自动加锁,析构时自动解锁,防止忘解锁的事情发生。lock_guard是个 类模板,形如其名托管互斥量。后面的几个互斥量基本都用这种方法。
std::mutex u_lock;
void func(int a)
{
std::lock_guard locker(u_lock);
for (int i = 0; i < 10; ++i)
std::cout << a << endl;
}
需要说明的是,还是尽量不要使用递归互斥量的好
(1)需要用到递归锁定的多线程,往往可以简化为迭代。允许递归容易放纵复杂逻辑产生。
(2)递归锁效率一般低一些。
(3)递归超过一定数目再lock进行调用会抛出std::system错误
std::timed_mutex u_lock;
void func(int a)
{
std::chrono::milliseconds timeout(100);
while (1)
{
if (u_lock.try_lock_for(timeout))
{
///...///
}
}
}
成员函数 | 说明 |
---|---|
notify_one | 通知一个等待线程 |
notify_all | 通知全部等待线程 |
wait | 阻塞当前线程直到被唤醒 |
wait_for | 阻塞当前线程直到被唤醒或超过指定的等待时间(长度) |
wait_until | 阻塞当前线程直到被唤醒或到达指定的时间(点) |
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.
//下面是重点
void do_print_id(int id)
{
std::unique_lock lck(mtx); //独占锁
while (!ready) // 如果标志位不为 true, 则等待...
cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
// 线程被唤醒, 继续往下执行打印线程编号id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_all(); // 通知唤醒所有线程.与上面额wait函数有关。
}
//上面是重点。main函数就是为了生成10个线程。每个线程先死循环,之后突然运行go()打开死循环。
int main()
{
std::thread threads[10];
//下面开10个线程:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
go(); // go!
for (auto & th : threads) //直接诶是
th.join();
return 0;
}