施磊老师高级c++(四)

发布于:2025-03-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

c++11 知识点汇总

一、C++11常用关键知识点梳理

1. 关键字和语法

  1. auto:可以根据右值,推导出右值的类型,然后左边变量的类型就已知了

  2. nullptr:给指针专用(能够和整数进行区别);之前的NULL是一个宏定义#define NULL 0,在代码上无法区分整数和指针地址

  3. foreach

    语句:可以遍历数组(底层是指针遍历),容器(底层是迭代器遍历)等

    for(Type val : container) => 底层就是通过指针或者迭代器来实现的
    	cout<<val<<" ";
    
  4. 右值引用:move移动语义函数和forward类型完美转发函数

  5. 模板的一个新特性:typename... A 表示可变参(类型参数)

2. 绑定器和函数对象

  • function:函数对象
  • bind:绑定器
  • bind1stbind2nd 只能结合 二元函数对象=>一元函数对象 – 非常有限
  • lambda表达式

3. 智能指针

shared_ptrweak_ptr----带引用计数, ---- 分强弱智能指针, weak_ptr指针.lock()可以提升为强智能指针
不带引用计数虽然有好几个, 但是 推荐使用 unique_ptr

4. 容器

  • set和map:红黑树,增删查O(logn) – 以前c++标准库就有
  • unorder_set和unorder_map:哈希表,增删查O(1) ---- c++11 新增
  • array:数组,固定大小,不可扩容。区别于vector ---- c++11 新增, 需要确保数量已知
  • forward_list:前向链表,单链表。list是双向链表 ---- c++11 新增

推荐使用vector和list, 更灵活, 具体情况具体看待

二、C++语言级别支持的多线程编程 – 本节重点

linux下的 线程函数, 在c++里并不适用

C++语言级别的多线程编程 =>代码可以跨平台:windows/linux/mac

主要内容: thread/mutex/condition_variable–线程, 互斥, 条件变量
: lock_quard, unique_lock
原子类型: atomic
睡眠: sleep_for

C++语言层面 thread--可以根据系统, 使用不同的底层(底层用的还是下面平台的方法) 
   windows        linux:
      |             |
createThread    pthread_create

需要包含的头文件#include <thread>-----linux是 pthread

1. 通过thread类编写C++多线程程序

  1. 主要函数:

    std::thread: 创建线程对象, ---- 类似于 pthread_create

    std::this_thread::sleep_for(std::chrono::seconds(2)) : 线程睡眠2s – 类似于 sleep(2)

    线程对象.join : 回收等待子线程, 在c++里 不分离线程的化, 必须回收等待, 这个与 linux不同----非常严格

    线程对象.detach: 分离线程

  2. 怎么创建启动一个线程?
    std::thread定义一个线程对象,传入线程所需要的线程函数和参数,线程自动开启

  3. 子线程如何结束?
    子线程函数运行完成,线程就结束了

  4. 主线程如何处理子线程?
    t.join():等待t线程结束,当前线程继续往下运行
    t.detach():把t线程设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束,类似于守护线程

#include <iostream>
#include <thread>
using namespace std;

void threadHandle1(int time)
{
	//让子线程睡眠time秒
	std::this_thread::sleep_for(std::chrono::seconds(time));
	cout << "hello threadHandle1!" << endl;
}

void threadHandle2(int time)
{
	//让子线程睡眠time秒
	std::this_thread::sleep_for(std::chrono::seconds(time));
	cout << "hello threadHandle2!" << endl;
}

int main()
{
	// 创建了一个线程对象,传入一个线程函数(作为线程入口函数), 新线程就开始运行,没有先后顺序,随着CPU的调度算法执行
	std::thread t1(threadHandle1, 1);
	std::thread t2(threadHandle2, 2);

	// 主线程运行到这里,等待子线程结束,主线程继续往下运行
	t1.join();
	t2.join();


	// 把子线程设置为分离线程

	//t1.detach();
	//t2.detach();

	cout << "main thread done!" << endl;

	/*
	主线程运行完成时,会查看当前进程是否还有未运行完成的子线程
	如果有未运行完成的子线程,那么进程就会异常终止
	*/
	return 0;
}

2. 线程间互斥锁与死锁 – 对应linux多线程互斥锁

c++ thread 模拟车站三个窗口买票的 程序

代码:

– 不加互斥锁, 数据是乱的, 会同时同一时间 访问 某个量

#include <iostream>
#include <thread>
#include <list>
using namespace std;

/*
c++ thread 模拟车站三个窗口买票的 程序
*/

int ticketCount = 10; // 车站有10张车票,由三个窗口一起卖票

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		cout << "窗口:" << index << "卖出第:" << 11-ticketCount << "张票!" << endl;
		ticketCount--;
		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}

int main()
{
	list<std::thread> tlist;  // 使用双向链表
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTicket, i));
	}
		

	for (auto& t : tlist)
	{
		t.join();
	}
		
	cout << "所有窗口卖票结束!" << endl;
	return 0;
}
竞态条件

指的是多个线程或进程同时访问共享资源时,程序的执行结果依赖于线程或进程的执行顺序,从而导致不可预测的行为或错误。

线程互斥mutex---- 跟linux使用非常像

要对线程安全进行保障,这就需要线程间的互斥,使用互斥锁,需要包含头文件#include <mutex>

.lock()

.unlock()

注意 : 加锁和解锁的位置! 非常影响打印的效果

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;

/*
c++ thread 模拟车站三个窗口买票的 程序
*/

int ticketCount = 10; // 车站有10张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	//mtx.lock();//这里是不行的, 导致 一个线程 把票全卖了
	while (ticketCount > 0)
	{
		mtx.lock();
		if (ticketCount > 0)  // 必须再次判断, 因为是先进来循环,才等锁, 会引发另一个卖完了, 这个还在循环里, 还会卖
		{
			cout << "窗口:" << index << "卖出第:" << 11 - ticketCount << "张票!" << endl;
			ticketCount--;
		}
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		
	}
	
}


int main()
{
	list<std::thread> tlist;  // 使用双向链表
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTicket, i));
	}
		

	for (auto& t : tlist)
	{
		t.join();
	}
		
	cout << "所有窗口卖票结束!" << endl;
	return 0;
}

临界区(Critical Section) 是多线程编程中的一个重要概念,指的是访问共享资源(如变量、数据结构、文件等)的一段代码。临界区中的代码需要被保护,以确保同一时间只有一个线程可以执行这段代码,从而避免竞态条件(Race Condition)和数据不一致的问题。

要保证临界区代码段 原子操作

死锁问题

程序如果在在中间出现问题, unlock就不会执行到了,会被阻塞加锁那里, 会导致死锁.

c++11 提供了lock_guardunique_lock 解决死锁问题

lock_guard-保证所有线程都能释放锁

lock_guardlock_guard<std::mutex> lock(mutex锁名); 构造会自动上锁,析构会自动释放。拷贝构造与赋值重载函数被删除掉了,类比智能指针scoped_ptr

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;

/*
c++ thread 模拟车站三个窗口买票的 程序
*/

int ticketCount = 10; // 车站有10张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	//mtx.lock();//这里是不行的, 导致 一个线程 把票全卖了
	while (ticketCount > 0)
	{
		//mtx.lock();
		{

			lock_guard<std::mutex> lock(mtx);   // 出作用域自动析构解锁
			if (ticketCount > 0)
			{
				cout << "窗口:" << index << "卖出第:" << 11 - ticketCount << "张票!" << endl;
				ticketCount--;
			}
		}
		//mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		
	}
	
}


int main()
{
	list<std::thread> tlist;  // 使用双向链表
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTicket, i));
	}
		

	for (auto& t : tlist)
	{
		t.join();
	}
		
	cout << "所有窗口卖票结束!" << endl;
	return 0;
}
unique_lock

unique_lock:构造会自动上锁,析构会自动释放。拷贝构造与赋值重载函数被删除掉了,提供了带右值引用版本的,类比智能指针unique_ptr

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;

/*
c++ thread 模拟车站三个窗口买票的 程序
*/

int ticketCount = 10; // 车站有10张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	//mtx.lock();//这里是不行的, 导致 一个线程 把票全卖了
	while (ticketCount > 0)
	{
		//mtx.lock();
		{

			//lock_guard<std::mutex> lock(mtx);   // 出作用域自动析构解锁
			unique_lock<std::mutex> lock(mtx);   // 出作用域自动析构解锁
			if (ticketCount > 0)
			{
				cout << "窗口:" << index << "卖出第:" << 11 - ticketCount << "张票!" << endl;
				ticketCount--;
			}
		}
		//mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		
	}
	
}


int main()
{
	list<std::thread> tlist;  // 使用双向链表
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTicket, i));
	}
		

	for (auto& t : tlist)
	{
		t.join();
	}
		
	cout << "所有窗口卖票结束!" << endl;
	return 0;
}

3. 线程间同步通信-生产者消费者模型

多线程编程两个问题:

  1. 线程间的互斥

    竞态条件导致=>对临界区代码段=>其原子操作=>添加互斥锁mutex、轻量级的无锁实现(CAS)

    Linux下strace ./a.out(程序启动的跟踪打印的命令)会发现 c++代码底层使用的还是 linux的 pthread_mutex_t

  2. 线程间的同步通信

    线程间不通信的话,每个线程受CPU的调度,没有任何执行上的顺序可言,线程1和线程2是根据CPU调度算法来的,两个线程都有可能先运行,是不确定的,线程间的运行顺序是不确定的

通信就是:

  • 线程1和线程2一起运行,线程2要做的事情必须先依赖于线程1完成部分的事情,然后线程1告诉线程2这部分东西做好了,线程2就可以继续向下执行了
  • 或者是线程1接下来要做某些操作,这些操作需要线程2把另外一部分事情做完,然后通知一下线程1它做完了,然后线程1才能做这些操作。
生产者-消费者线程模型

注意C++ STL中所有的容器都不是线程安全的,都需要进行封装。在这个例子中把queue封装成了Queue

错误案例

先看一个非常简便的 例子 : 这个例子 问题很多:生产者空,消费者还要消费
生产者和消费者 不交流

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
using namespace std;

std::mutex mtx;

// 生产者生产一个物品,通知消费者消费一个;消费完了,消费者再通知生产者继续生产物品
class Queue
{
public:
	//生产物品
	void put(int val)
	{
		lock_guard<std::mutex> suo(mtx);
		que.push(val);

		cout << "生产者 生产:" << val <<"号物品" << endl;
	}

	// 消费物品
	int get()
	{
		lock_guard<std::mutex> suo(mtx);

		int val = que.front();
		que.pop();

		cout << "消费者 消费:" << val <<"号物品" << endl;
		return val;
	}

private:
	queue<int> que;
	
};

void producer(Queue* que)	// 生产者线程
{
	for (int i = 1; i <= 10; ++i)
	{
		que->put(i);
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

void consumer(Queue* que)	// 消费者线程
{
	for (int i = 1; i <= 10; ++i)
	{
		que->get();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	Queue que;	// 两个线程共享的队列

	
	std::thread t1(producer, &que);
	std::thread t2(consumer, &que);

	t1.join();
	t2.join();

	return 0;
}
正确案例-- 使用条件变量

信号量 (这是指c中的信号量)虽然可以做, 但是 条件变量(c里有, 但是c++11提供了更好的封装)更好用

条件变量:有两种

  • 功能:条件变量允许线程等待某个条件为真时才继续执行,通常与互斥锁结合使用。

  • 使用条件变量需要包含头文件#include <condition_variable>

  • .wait() 必须传入 unique_lock 类型的 加锁, 不能是别的类型

  • std::condition_variable

    • 必须与 std::unique_lock<std::mutex> 配合使用。
    • 性能更高,但灵活性较低。
  • std::condition_variable_any

    • 可以与任何满足基本要求的锁类型配合使用。
    • 灵活性更高,但性能较低。
  • 核心操作:

    条件变量的核心操作
    wait():
    
    使当前线程进入等待状态,直到被通知。
    
    通常与谓词(Predicate)一起使用,以避免虚假唤醒。
    
    notify_one():
    
    唤醒一个等待的线程。
    
    notify_all():
    
    唤醒所有等待的线程。
    
  • 条件变量, 虽然没有明确的 状态术语, 不过 一般来说 : wait() 使线程进入等待状态,在此期间它会释放关联的互斥锁并挂起执行,直到收到 notify_one()notify_all() 的通知后被唤醒,并尝试重新获取锁;而阻塞状态指的是线程在尝试获取锁时发现锁已被其他线程持有,因此无法继续执行,必须等待锁释放后才能继续运行。
    通知后, 其它线程得到该通知,就会从等待状态(条件达成)=>阻塞状态,之后获取互斥锁继续执行----有点乱, 大概明白就行

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

std::mutex mtx;  // 定义互斥锁, 线程间的 同步操作
std::condition_variable cv; // 定义条件变量 线程间的 通信操作

// 生产者生产一个物品,通知消费者消费一个;消费完了,消费者再通知生产者继续生产物品
class Queue
{
public:
	//生产物品
	void put(int val)
	{
		/*lock_guard<std::mutex> guard(mtx);*/

		// que不为空,生产者应该通知消费者去消费
		unique_lock<std::mutex> lck(mtx);
		while (!que.empty())  
		{
			// que不为空,生产者应该通知消费者去消费, 消费者消费完了,生产者再继续生产
			// 生产者线程进入#1等待状态,并且#2把mtx互斥锁释放掉
			
			cv.wait(lck); //传入一个互斥锁,当前线程挂起,处于等待状态,并且释放当前锁
		}
		que.push(val);

		cv.notify_all();  // 通知其他线程 , 生产了物品, 可以消费
		//其它线程得到该通知,就会从等待状态变为阻塞状态,之后获取互斥锁继续执行

		cout << "生产者 生产:" << val <<"号物品" << endl;
	}

	// 消费物品
	int get()
	{
		/*lock_guard<std::mutex> guard(mtx);*/
		unique_lock<std::mutex> lck(mtx);
		//消费者线程发现que是空的,通知生产者线程先生产物品
		//消费者线程进入等待状态,并且把mtx互斥锁释放掉
		while (que.empty())
		{
			cv.wait(lck); // 循环等待, 并释放互斥锁
		}
		int val = que.front();
		que.pop();
		cv.notify_all();

		cout << "消费者 消费:" << val <<"号物品" << endl;
		return val;
	}

private:
	queue<int> que;
};

void producer(Queue* que)	// 生产者线程
{
	for (int i = 1; i <= 10; ++i)
	{
		que->put(i);
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

void consumer(Queue* que)	// 消费者线程
{
	for (int i = 1; i <= 10; ++i)
	{
		que->get();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	Queue que;	// 两个线程共享的队列

	
	std::thread t1(producer, &que);
	std::thread t2(consumer, &que);

	t1.join();
	t2.join();

	return 0;
}

**特别注意: **

  • 锁的位置对多线程程序的正确性至关重要。
  • 锁必须覆盖整个操作,包括对共享资源的访问和条件变量的等待。
  • 如果将锁放到 while 循环内部,会导致竞争条件和输出乱序。
  • 正确的锁位置应覆盖整个操作,确保线程间的同步和共享资源的安全性。

2.4 再谈lock_guard和unique_lock

主要是为了再讲讲lock_guard和unique_lock, condition_varivable, wait,notify_all

std::mutex mtx;

mtx.lock();

mtx.unlock();

普通互斥锁的缺点: 有可能中间走掉了,导致没有unlock(),不安全

lock_guard<std::mutex> guard(mtx); 可以出了作用域 自动析构

lock_guard不可能用在函数参数传递或者返回过程中,因为拷贝构造和赋值函数都被删除了

只能用在简单的加锁解锁临界区代码段的互斥操作中,出作用域析构自动释放锁

unique_lock<std::mutex> lck(mtx);搭配条件变量使用

unique_lock : 不仅可以使用在简单的加锁解锁临界区代码段的互斥操作中,还能用在函数调用过程中,因为其虽然删除了拷贝构造和赋值函数,但是提供了带右值引用版本的

总结: lock_guard 适用于 无线程通信的 情况

unique_lock 搭配 condition_variable使用, 可以 wait和notify_all,notify_one 结合使用

/*
通知在cv上等待的线程,条件成立了,起来干活了!
其它在cv上等待的线程,收到通知,
从等待状态 -> 到阻塞状态(不能直接运行) 
只有当前线程释放锁了,其他线程获取互斥锁了,线程才能继续往下执行
*/
cv.notify_all();

2.5 基于CAS操作的atomic原子类型

本节重点是: atomic 这个模板类定义的原子类型变量 , 这种类型的变量的操作都将是 是原子操作

这意味着:
std::atomic 类型的变量进行的所有操作(例如读取、写入、增减等)都是不可中断的,不会被其他线程的操作打断。

  1. 窗口卖票的代码, count+±- 操作, 是线程不安全的, 之前 使用了 互斥锁 保证线程安全

  2. 互斥锁是比较重的,临界区代码复杂时可以使用;但现在我们只是做一个加加减减的操作,还是需要一些轻量级原子操作的操作
    解决办法:使用CAS保证上面加加减减操作的原子特性就足够了,CAS也叫做无锁操作

  3. 什么是 原子操作?

    原子操作(Atomic Operation)指的是一系列操作在执行期间不可被中断的操作,它是一个不可分割的操作单元。也就是说,当一个原子操作开始执行时,它要么完全执行成功,要么完全不执行,不会被其他线程中断或打断,保证了操作的完整性和一致性。

  4. 什么是CAS?

    CAS(Compare-And-Swap) 是一种原子操作,用于在多线程环境中实现无锁的线程同步。其核心概念是:比较和交换(exchange/swap),即在执行操作时,先比较目标变量的当前值与预期值是否相等,如果相等则将目标变量的值替换为新值,否则不做任何修改。
    CAS(Compare-And-Swap) 是一种原子操作,用于实现无锁编程(Lock-Free Programming)。它是一种硬件级别的同步机制,通常用于多线程环境中,确保对共享数据的操作是原子的(即不可分割的)。

  5. CAS 就是无锁操作, 面试的 无锁队列啥的, 就是CAS

  6. std::atomic 这是一个模板, 所以需要搭配<>使用

  7. volatile 是 C 和 C++ 中的一个关键字,用于告诉编译器某个变量的值可能会在程序外部发生变化,因此编译器在优化时不能对该变量进行某些假设(禁止优化),必须每次直接从内存中读取其值。 防止缓存

  8. 原子操作: 实际就是 所有的读写 不会被其他线程中断

  9. std::atomic_bool 是 C++11 标准引入的一个别名类型,它是 std::atomic<bool> 的简写或别名。std::atomic_bool 在 C++ 中是一个 类型别名,通常用于让代码更加简洁,便于编写与使用。

头文件:#include <atomic>

代码示例:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

volatile std::atomic_bool isReady = false;  // atomic的特例化版本

//volatile std::atomic_int ticketCount = 0;  // volatile  防止多线程变量 进行缓存
volatile std::atomic<int> ticketCount = 0;  // 一般这么用 

void task()
{
	while (!isReady)
		std::this_thread::yield(); // 让当前线程 自愿地让出 CPU 的控制权(cpu时间片),允许同一线程的其他线程获得执行机会

	for (int i = 0; i < 100; ++i)
		ticketCount++;
}

int main()
{
	list<std::thread> tlist;

	for (int i = 0; i < 10; ++i)
		tlist.push_back(std::thread(task));

	std::this_thread::sleep_for(std::chrono::seconds(1));
	cout << "ticketCount:" << ticketCount << endl;	// ticketCount:0

	isReady = true;  // 这里再执行++

	for (auto& t : tlist)
		t.join();

	cout << "ticketCount:" << ticketCount << endl;	// ticketCount:1000

	return 0;
}

2.6额外补充:CAS的成员方法—课里没讲

CAS(Compare-And-Swap) 是一种原子操作,用于在并发编程中保证线程安全,常用于实现无锁数据结构和算法。它的基本原理是比较某个变量的当前值是否等于预期值,如果相等,则交换为新的值。这个操作是原子的,因此能有效避免线程竞争。

在 C++ 中,CAS 通常通过标准库中的 std::atomic 来实现,它提供了原子操作的接口,包括 compare_exchange_weakcompare_exchange_strong 两种形式。

基本语法和使用示例

假设你有一个 std::atomic<int> 类型的变量,并且你想使用 CAS 来修改它。

#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> x(5); // 创建一个原子整数,初始值为5
    int expected = 5;       // 预期值为5
    int new_value = 10;     // 新值为10

    // 使用 compare_exchange_strong 执行 CAS 操作
    bool success = x.compare_exchange_strong(expected, new_value);

    if (success) {
        std::cout << "CAS 操作成功,新的值是: " << x.load() << std::endl;
    } else {
        std::cout << "CAS 操作失败,当前值是: " << x.load() << std::endl;
    }

    return 0;
}
CAS 操作解释:
  1. x.compare_exchange_strong(expected, new_value)
    • 比较 x 的当前值和 expected 的值。如果它们相等,x 将被更新为 new_value,并返回 true
    • 如果 x 的当前值不等于 expected,则 expected 将被更新为 x 的当前值,操作失败,返回 false
    • compare_exchange_strong 是一种强操作,它会进行多个重试,直到操作成功或遇到某些停止条件。
  2. expected 变量在 CAS 操作后,可能会被修改为 x 当前的值,因此你需要在失败时查看 expected 的新值。

compare_exchange_weak vs compare_exchange_strong

  • compare_exchange_strong:确保操作尽可能成功地执行,可能会进行多次重试,直到操作成功。
  • compare_exchange_weak:不保证每次都能执行,可能会失败并返回 false,适用于无锁算法中可能需要退让的情形。

代码解析:

  • 成功的 CAS 操作expectedx 的值相等时,x 会被更新为新值,successtrue
  • 失败的 CAS 操作expectedx 的值不相等时,expected 会更新为 x 的当前值,successfalse

注意事项:

  1. ABA 问题:CAS 操作可能会出现 ABA 问题,指的是一个值从 A 改为 B,然后又变回 A,这时 CAS 可能会误认为值没有变化。解决方法之一是使用带有版本号的 CAS 或者增加标记。
  2. 高并发问题:CAS 操作可能导致忙等(自旋),如果操作频繁失败,可能会消耗大量的 CPU 资源。此时,可以考虑结合自旋锁或引入等待机制。
总结
  • CAS 是一种高效的原子操作,用于并发编程中无锁算法的实现。
  • 在 C++ 中,std::atomic 提供了 CAS 操作的接口,如 compare_exchange_strongcompare_exchange_weak
  • 使用 CAS 时,需要注意 ABA 问题和自旋的效率问题。