C和C++的线程用法区别可以从多个角度进行比较,包括线程创建与管理、线程同步、传递对象、异常处理等方面。以下是C与C++线程用法的全面总结:
C:
pthread_create() 创建线程,使用 pthread_join() 或 pthread_detach() 来管理线程。示例(C语言,使用 pthread):
#include <pthread.h>
#include <stdio.h>
void* thread_function(void* arg) {
printf("Thread is running.\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, NULL); // 等待线程结束
return 0;
}C++:
std::thread 类来管理线程。示例(C++语言,使用 std::thread):
#include <iostream>
#include <thread>
void thread_function() {
std::cout << "Thread is running." << std::endl;
}
int main() {
std::thread t(thread_function); // 创建线程
t.join(); // 等待线程结束
return 0;
}C:
pthread_mutex_t, pthread_cond_t 等机制。示例(C语言,使用 pthread 互斥锁):
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t lock;
void* thread_function(void* arg) {
pthread_mutex_lock(&lock); // 加锁
printf("Thread is running.\n");
pthread_mutex_unlock(&lock); // 解锁
return NULL;
}
int main() {
pthread_t thread;
pthread_mutex_init(&lock, NULL);
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, NULL);
pthread_mutex_destroy(&lock);
return 0;
}C++:
std::mutex, std::lock_guard, std::condition_variable 和 std::atomic。std::mutex 用于互斥锁,std::lock_guard 简化了锁的使用,自动管理锁的加锁与释放。std::condition_variable 实现线程之间的通知和等待机制。示例(C++语言,使用 std::mutex):
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void thread_function() {
std::lock_guard<std::mutex> guard(mtx); // 自动加锁
std::cout << "Thread is running." << std::endl;
}
int main() {
std::thread t(thread_function);
t.join();
return 0;
}C:
void* 类型的参数,通过指针传递数据或对象。示例(C语言,传递结构体指针):
#include <pthread.h>
#include <stdio.h>
typedef struct {
int a;
int b;
} MyStruct;
void* thread_function(void* arg) {
MyStruct* data = (MyStruct*)arg;
printf("a = %d, b = %d\n", data->a, data->b);
return NULL;
}
int main() {
pthread_t thread;
MyStruct obj = {10, 20};
pthread_create(&thread, NULL, thread_function, (void*)&obj);
pthread_join(thread, NULL);
return 0;
}C++:
std::shared_ptr 和 std::unique_ptr)来传递对象。示例(C++语言,使用 std::shared_ptr):
#include <iostream>
#include <thread>
#include <memory>
class MyClass {
public:
int a, b;
MyClass(int x, int y) : a(x), b(y) {}
void print() { std::cout << "a = " << a << ", b = " << b << std::endl; }
};
void thread_function(std::shared_ptr<MyClass> obj) {
obj->print();
}
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>(10, 20);
std::thread t(thread_function, obj); // 传递智能指针
t.join();
return 0;
}C:
setjmp 和 longjmp 来实现。C++:
try-catch 语句来捕获和处理异常。std::thread 类中的线程异常不会自动传播,需要显式地捕获异常。示例(C++语言,使用异常处理):
#include <iostream>
#include <thread>
void thread_function() {
try {
throw std::runtime_error("Thread error");
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
}
int main() {
std::thread t(thread_function);
t.join();
return 0;
}C:
pthread_join() 或 pthread_detach() 来显式等待线程完成或分离线程。C++:
std::thread 在其对象超出作用域时会自动销毁。如果线程对象没有显式地调用 join() 或 detach(),则会抛出异常(std::terminate())。示例(C++语言,自动销毁线程):
#include <iostream>
#include <thread>
void thread_function() {
std::cout << "Thread is running." << std::endl;
}
int main() {
std::thread t(thread_function);
t.join(); // 等待线程结束
return 0; // t超出作用域后自动销毁
}std::thread 和同步机制,可以在多种平台上使用(如Windows、Linux、macOS等),具有较好的跨平台性。特性 | C语言 | C++语言 |
|---|---|---|
线程库 | 依赖外部库(pthreads、Windows API) | 原生支持(std::thread) |
线程创建 | 使用 pthread_create | 使用 std::thread |
线程同步 | 使用 pthread_mutex, pthread_cond 等 | 使用 std::mutex, std::lock_guard, std::atomic 等 |
传递对象 | 通过指针传递(手动管理内存) | 通过值、引用、智能指针传递(更灵活、安全) |
异常处理 | 没有内建异常处理机制 | 支持 try-catch 语句,异常可以在线程中捕获 |
线程生命周期管理 | 手动管理,使用 pthread_join 或 pthread_detach | 自动销毁,未 join() 或 detach() 会调用 std::terminate |
跨平台性 | 需要依赖平台特定的线程库 | 跨平台支持更好,使用标准线程库 |
C++提供了更高级的线程管理机制,使得多线程编程更加简洁、安全和高效,而C则需要更多手动管理。
在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在 并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。thread文档


函数名 | 功能 |
|---|---|
thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 |
thread(fn, args1, args2, ...) | 构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的 参数 |
get_id() | 获取线程id |
jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程。 |
jion() | 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行 |
detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离 的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
Example
// thread example
#include <iostream> // std::cout
#include <thread> // std::thread
void foo()
{
// do stuff...
}
void bar(int x)
{
// do stuff...
}
int main()
{
std::thread first (foo); // spawn new thread that calls foo()
std::thread second (bar,0); // spawn new thread that calls bar(0)
std::cout << "main, foo and bar now execute concurrently...\n";
// synchronize threads:
first.join(); // pauses until first finishes
second.join(); // pauses until second finishes
std::cout << "foo and bar completed.\n";
return 0;注意:
1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
#include <thread>
int main()
{
std::thread t1;
cout << t1.get_id() << endl;
return 0;
}get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中 包含了一个结构体:
3. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。 线程函数一般情况下可按照以下三种方式提供:(C与C++的区别,C++传的对象更多了 )
4. thread类是防拷贝的(不支持拷贝赋值和拷贝构造,支持移动复制和移动构造),不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5. 可以通过joinable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效

void func(int x)
{
for (int i = 0; i < x; i++)
{
cout << i << endl;
}
}
int main() {
thread t1(func, 3); // 调用 int 版本
//不要忘了等待
t1.join();
return 0;
}这里要注意传重载函数时编译器无法确定,如果遇到这种情况,通常会出现编译错误。解决办法就是最好选择用Lambda 。

void func(int x)
{
for (int i = 0; i < x; i++)
{
cout << i << endl;
}
}
void func(int x, const string &s)
{
for (int i = 0; i < x; i++)
{
cout << i << endl << s << endl;
}
}
int main() {
thread t1([]()
{
func(1);
}
);
thread t2([]()
{
func(2,"ljw");
}
);
//不要忘了等待
t1.join();
t2.join();
return 0;
}
std::thread::get_id(获取线程id)
int main() {
thread t1([]()
{
func(1);
}
);
cout << "t1->" << t1.get_id() << endl;
thread t2([]()
{
func(2,"ljw");
}
);
cout << "t2->" << t2.get_id() << endl;
//不要忘了等待
t1.join();
t2.join();
return 0;
}std::this_thread::get_id(在线程里面获取自己的线程)
void func(int x)
{
for (int i = 0; i < x; i++)
{
cout << i << endl;
cout << this_thread::get_id() << endl;
}
}
void func(int x, const string &s)
{
for (int i = 0; i < x; i++)
{
cout << i << endl << s << endl;
cout << this_thread::get_id() << endl;
}
}线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在 线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。(用&接收,最好就要把ref加上)
错误样例
在线程函数中对x修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝
void func(int& x)
{
x++;
}
int main()
{
int x = 1;
thread t1(func, x);
cout << x;
//不要忘了等待
t1.join();
return 0;
}
解决方案1,如果想要通过形参改变外部实参时,必须借助std::ref()函数(用&接收,最好就要把ref加上)
void func(int& x)
{
x++;
}
int main()
{
int x = 1;
thread t1(func, ref(x));
//cout << x;//这里要注意要在join以后再打印,要不然main这个线程可能会运行的快于func
t1.join();
cout << x;
return 0;
}解决方案2,地址的拷贝
void func(int* x)
{
*x += 1;
}
int main()
{
int x = 1;
thread t1(func, &x);
//cout << x;//这里要注意要在join以后再打印,要不然main这个线程可能会运行的快于func
t1.join();
cout << x;
return 0;
}注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。
int main()
{
size_t n;
cin >> n;
//创建n个线程执行Print
vector<thread> vthd(n);
return 0;
}给n个值,第二个值,不给上图这里就是thread,相当于创建了n个线程对象,但没有执行

不支持拷贝赋值和拷贝构造,支持移动赋值和移动构造

下面用多线程代码展示移动赋值
思路:
void func(int x)
{
for (int i = 0; i < x; i++)
{
cout << i << endl;
}
}
int main()
{
size_t n;
cin >> n;
//创建n个线程执行func
vector<thread> vthd(n);
for (auto& thd : vthd)
{
//移动赋值
thd = thread(func, 2);
}
for (auto& thd : vthd)
{
thd.join();
}
return 0;
}
thread t1(func, 3);
thread t2(move(t1));在C++中,std::move 是一个用于将对象从左值转化为右值的标准库函数。它不会移动对象本身,而是通过转换对象的类型来启用移动语义。
主要用途:
std::move 使得对象能够通过移动而不是复制的方式传递给函数或赋值给其他对象。它本质上是一个类型转换工具,转化一个对象为右值引用。
std::vector、std::string 等类的数据转移。
语法:
std::move(x)x 是一个左值(普通变量或对象)。
std::move(x) 会将 x 转换为一个右值引用(T&&),即允许资源被“移动”而不是被复制。
示例代码:
#include <iostream>
#include <vector>
#include <utility> // for std::move
class MyClass {
public:
MyClass() {
std::cout << "MyClass Constructor\n";
}
MyClass(const MyClass& other) {
std::cout << "Copy Constructor\n";
}
MyClass(MyClass&& other) noexcept {
std::cout << "Move Constructor\n";
}
};
int main() {
MyClass obj1;
MyClass obj2 = std::move(obj1); // Move constructor called
std::vector<int> v1 = {1, 2, 3, 4};
std::vector<int> v2 = std::move(v1); // Move the contents of v1 into v2
std::cout << "v1 size: " << v1.size() << "\n"; // v1 is now empty, valid but unspecified state
std::cout << "v2 size: " << v2.size() << "\n"; // v2 contains the data
}注意事项:
std::move 后,原对象进入“已移交”状态,不能再被访问或操作。调用 std::move 不会对对象做任何改变,它只是通过类型转换来触发移动语义。
std::move 常用于返回对象时启用返回值优化(RVO)或者移动语义,使得返回值的复制被替换为移动。
std::forward 的关系: std::forward 用于在完美转发中保持对象的左值或右值属性,而 std::move 总是将对象转化为右值。
总结:
std::move 是一个用于启用移动语义的工具,它允许对象的资源从一个位置“移动”到另一个位置而不是复制,从而提高程序的性能,特别是对于大型对象或容器。
int main()
{
size_t n1 = 10000;
size_t n2 = 10000;
int x = 0;
cout << n1 << "+" << n2 << endl;
thread t1([&]()
{
for (int i = 0; i < n1; i++)
{
x++;
}
}
);
thread t2([&]()
{
for (int i = 0; i < n2; i++)
{
x++;
}
}
);
t1.join();
t2.join();
cout << x;
return 0;
}上述代码有线程安全的问题 ,有可能两个线程会同时++x,解决办法用锁
注意:lambda可以不用考虑ref的问题,[&]是获取所有的地址,int i = 1;[&,i]这里的i是单独获取值,在lambda中不会改变i的值,只是传值而已
mutex的种类
在C++11中,Mutex总共包了四个互斥量的种类:
1. std::mutex C++11提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。mutex最常用的三个函数:
函数名 | 函数功能 |
|---|---|
lock() | 上锁:锁住互斥量 |
unlock() | 解锁:释放对互斥量的所有权 |
try_lock() | 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞 |
注意,线程函数调用lock()时,可能会发生以下三种情况:
线程函数调用try_lock()时,可能会发生以下三种情况:
2. std::recursive_mutex
其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权, 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,
std::recursive_mutex 的特性和 std::mutex 大致相同。
3. std::timed_mutex
比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。
接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超 时(即在指定时间内还是没有获得锁),则返回 false。
接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住, 如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指 定时间内还是没有获得锁),则返回 false。
4. std::recursive_timed_mutex

mutex | 普通锁 |
|---|---|
recursive_mutex | 递归互斥锁(递归时就死锁了,实质是判断是不是当前线程,是当前线程就lock,不是就不lock) |
timed_mutex | 时间互斥锁 |
recursive_timed_mutex | 递归时间互斥锁 |
也是一个类

int main()
{
size_t n1 = 10000;
size_t n2 = 10000;
int x = 0;
mutex mtx;
cout << n1 << "+" << n2 << endl;
thread t1([&]()
{
for (int i = 0; i < n1; i++)
{
mtx.lock();
x++;
mtx.unlock();
}
}
);
thread t2([&]()
{
for (int i = 0; i < n2; i++)
{
mtx.lock();
x++;
mtx.unlock();
}
}
);
t1.join();
t2.join();
cout << x;
return 0;
}
不用lambda线程时传递注意用引用接收时并且要改变传过来的值时,要加ref
void func(size_t n, int& x,mutex& mtx)
{
for (int i = 0; i < 1000; i++)
{
mtx.lock();
x++;
mtx.unlock();
}
}
int main()
{
int x = 0;
mutex mtx;
thread t1(func,10, ref(x), ref(mtx));
thread t2(func, 100,ref(x), ref(mtx));
t1.join();
t2.join();
cout << x;
return 0;
}左值引用这里都要加ref,100和" "里的都是右值(thread里的参数会传给thread的构造函数,然后因为其底层原因,这些所有的值传递过去的时候,都会变成类似的右值)
try_lock是不阻塞,lock是阻塞
mutex | 普通锁 |
|---|---|
recursive_mutex | 递归互斥锁(递归时就死锁了,实质是判断是不是当前线程,是当前线程就lock,不是就不lock) |
timed_mutex | 时间互斥锁 |
recursive_timed_mutex | 递归时间互斥锁 |

0.3秒运行一次

如果抛异常了直接退出程序了但锁还没有解除,就形成了死锁
int main()
{
mutex mtx;
mtx.lock();
//抛异常了
mtx.unlock();
return 0;
}解决办法:lock_guard

把锁交给类对象进行管理,进行资源的释放等等,避免抛异常出问题(死锁)
模拟一个lock_guard
这里用模板是因为锁也有很多种锁,注意成员变量要用&
template<class Lock>
class LockGuard
{
public:
LockGuard(Lock& lk)
:_lk(lk)
{
_lk.lock();
}
~LockGuard()
{
_lk.unlock();
}
private:
Lock& _lk;
};会产生死锁代码实例
void func()
{
srand(time(0));
if (rand() % 3)
{
throw exception("异常");
}
else
{
cout << "func" << endl;
}
}
int main()
{
mutex mtx;
size_t n1 = 5;
size_t n2 = 5;
int x = 0;
thread t1([&]()
{
try
{
for (int i = 0; i < n1; i++)
{
mtx.lock();
//可能抛异常
func();
x++;
mtx.unlock();
}
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
);
return 0;
}
解决办法就是用std::lock_guard
lock_guard - C++ Reference (cplusplus.com)



unique_lock - C++ Reference (cplusplus.com)
两者区别,lock_guard只支持构造和析构 unique_lock支持手动解锁


代码示例
// unique_lock example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
std::mutex mtx; // mutex for critical section
void print_block (int n, char c) {
// critical section (exclusive access to std::cout signaled by lifetime of lck):
std::unique_lock<std::mutex> lck (mtx);
for (int i=0; i<n; ++i) { std::cout << c; }
std::cout << '\n';
}
int main ()
{
std::thread th1 (print_block,50,'*');
std::thread th2 (print_block,50,'$');
th1.join();
th2.join();
return 0;
}std::condition_variable
condition_variable - C++ Reference (cplusplus.com)


常用:
wait | condition_variable::wait - C++ Reference (cplusplus.com) |
|---|---|
notify_one(唤醒一个) | condition_variable::notify_one - C++ Reference (cplusplus.com) |
notify_all(唤醒所有) | condition_variable::notify_all - C++ Reference (cplusplus.com) |
这里注意要用unique_lock,所以只能用unqiue_lock,wait的时候会unlock
wait后会自动释放锁

被唤醒的时候,被唤醒线程马上又会拿到lock

wait的第二个参数的用法

在 C++ 中,std::condition_variable::wait 函数的第二个参数用于指定一个谓词条件,用来判断是否需要等待或是继续执行。这是为了避免虚假唤醒(spurious wakeups),即线程在没有被显式通知的情况下被唤醒。
wait 函数的基本用法如下:
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void thread_func()
{
std::unique_lock<std::mutex> lock(mtx);
// 使用wait的第二个参数作为条件,只有条件为false时才等待
cv.wait(lock, []{ return ready; });
// 当ready为true时,才会继续执行
// 继续执行后面的代码
}
void notify_func()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::lock_guard<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}在上面的例子中:
wait(lock, pred):
lock:一个 std::unique_lock<std::mutex> 对象,表示线程要持有的锁。pred:一个谓词条件,返回一个布尔值的函数或者可调用对象。在这个例子中是 [] { return ready; },它在 ready 为 true 时返回 true,否则返回 false。cv.wait(lock, pred) 会让线程在锁定条件变量的同时进入等待状态,直到谓词 pred 返回 true,线程才会被唤醒继续执行。
这种方法避免了虚假唤醒的情况,因为它在每次被唤醒后都会检查条件是否满足。如果不满足,线程会继续等待。
两个线程t1和t2,两个线程一个线程打印一次,交替执行
如何保证t1先运行
分析一下这个代码,t2可能会一直等待,因为如果t1跑的特别快时,唤醒的时候,t2还没有开始等待,那么当t1跑完的时候,t2就会在一直等待,没人唤醒,从而让程序崩溃

正确写法
没有要唤醒的就什么也不做

int main()
{
int x = 0;
mutex mtx;
condition_variable cv;
int flag = false;
//保证t1进程先运行
thread t1(
[&]()
{
for (int i=0;i<5;i++)
{
unique_lock<mutex> lck(mtx);
//如果是false,++x,且保证t1先跳过循环
while (flag)
{
cv.wait(lck);//wait时会释放锁unlock
};
x++;
cout << "t1->" << x << endl;
flag = true;
cv.notify_one();//如果没有等待notify_one那么就什么也不做
}
}
);
thread t2(
[&]()
{
for (int i = 0; i < 5; i++)
{
unique_lock<mutex> lck(mtx);
//保证t2后执行
while (!flag)
{
cv.wait(lck);
};
x++;
cout << "t2->" << x << endl;
flag = false;
cv.notify_one();
}
}
);
t1.join();
t2.join();
return 0;
}解释:首先他们会先竞争锁,如果t1拿到锁了(只有一个线程能拿到锁)那么t2就会在unique_lock这停下来,极端条件下,然后t1走了一个循环后,再拿到了锁,但flag是true要进行等待了,等待的时候会释放锁,然后t2就会拿到锁。 如果是t2先拿到了锁,会直接进入wait,然后t1执行,后面的就跟t1先拿到锁的情况一样了 wait会解锁
思路


第一种,1在锁上面,2执行完了解锁,就会唤醒你
第二种,不在锁上面,获取到锁了,但在wait上面,wait上面会解锁,然后t2执行,再notify t1,然后把锁交给t1
第三种,t1的时间片到了,在}不动了, 然后t2执行完后一定会wait,然后释放锁,t1有时间片后,就可以拿到锁
atomic - C++ Reference (cplusplus.com)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问 题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数 据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:
这种情况用加锁,会很消耗资源,临界时间时间段不适合用互斥锁,线程会频繁阻塞
要用自旋锁(库里没有)用原子的,一般都是内置类型的
int main()
{
size_t n1 = 10000;
size_t n2 = 10000;
int x = 0;
mutex mtx;
cout << n1 << "+" << n2 << endl;
thread t1([&]()
{
for (int i = 0; i < n1; i++)
{
mtx.lock();
x++;
mtx.unlock();
}
}
);
thread t2([&]()
{
for (int i = 0; i < n2; i++)
{
mtx.lock();
x++;
mtx.unlock();
}
}
);
t1.join();
t2.join();
cout << x;
return 0;
}原子类型
int main()
{
size_t n1 = 10000;
size_t n2 = 10000;
mutex mtx;
atomic<bool> flag = true;
atomic<int> x = 0;
cout << n1 << "+" << n2 << endl;
thread t1([&]()
{
for (int i = 0; i < n1; i++)
{
x++;
flag = false;
}
}
);
thread t2([&]()
{
for (int i = 0; i < n2; i++)
{
x++;
flag = false;
}
}
);
t1.join();
t2.join();
printf("%d\n", x);//错误打印方法
cout << x;
return 0;
}这里要注意的是
printf("%d\n", x);//错误打印方法
shared_ptr多线程问题
++的时候用原子的


只想保护*copy1,用{ }局部域

同时调用这一步

懒汉单例模式最简单(C++11之前不是线程安全的,C++11之后的是安全的)
C++11把

变成了原子操作

