(目录)


一、c++线程的回调函数

#include <iostream>
#include <thread>
#include <windows.h> // Linux下,头文件为 unistd.h
using namespace std;

void func(int n, const string &str) {
	for (int i = 1; i < 5; ++i) {
		cout << "NO. " << n << " Loop: " << i << " " << str << endl;
		Sleep(1000); // Linux下为sleep(1)
	}
}

class thread_f {
public:
	void operator()(int n, const string &str) {
		for (int i = 1; i < 5; ++i) {
			cout << "NO. " << n << " Loop: " << i << " " << str << endl;
			Sleep(1000); // Linux下为sleep(1)
		}
	}
};

class s_f {
public:
	static void func(int n, const string &str) {
		for (int i = 1; i < 5; ++i) {
			cout << "NO. " << n << " Loop: " << i << " " << str << endl;
			::Sleep(1000); // Linux下为sleep(1)
		}
	}
};

class Normal_f {
public:
	void func(int n, const string &str) {
		for (int i = 1; i < 5; ++i) {
			cout << "NO. " << n << " Loop: " << i << " " << str << endl;
			::Sleep(1000); // Linux下为sleep(1)
		}
	}
};

int main(int argc, const char **argv) {
	// 普通函数回调
	thread t1(func, 1, "testing(1)...");

	// 使用函数对象(仿函数)创建线程
	thread t2(thread_f(), 2, "testing(2)...");

	// 使用lambda函数创建线程
	auto f = [](int n, const string &str) {
		for (int i = 1; i < 5; ++i) {
			cout << "NO. " << n << " Loop: " << i << " " << str << endl;
			Sleep(1000); // Linux下为sleep(1)
		}
	};
	thread t3(f, 3, "testing(3)...");

	// 使用静态成员函数
	thread t4(s_f::func, 4, "testing(4)...");

	cout << "begin" << endl;
	for (int i = 0; i < 5; ++i) {
		cout << "main work " << endl;
		Sleep(1000); // Linux下为sleep(1)
	}

	// 使用普通成员函数
	// 必须保证对象的生命周期比子线程要长
	Normal_f n_f;
	thread t5(&Normal_f::func, &n_f, 5, "testing(5)...");

	cout << "end" << endl;

	// 回收线程资源
	t1.join();
	t2.join();
	t3.join();
	t4.join();
	t5.join();

	return 0;
}

Windows下Sleep(毫秒);Linux下sleep(秒) Linux下编译: g++ -o demo demo.cpp -std=c++11 -lpthread

  • thread(const thread&)=delete; 线程类删除了拷贝构造函数,不充许线程对象之间相互拷贝。

  • thread(thread&& other) noexcept; 线程类使用移动构造函数,将线程other的资源所有权转移给新创建的线程对象。

  • 赋值函数: thread& operator=(thread&& other) noexcept; thread& operator=(const other&)=delete; 线程中的资源不能被复制,如果other是右值,会进行资源所有权的转移,如果other是左值,禁止拷贝。

二、线程资源的回收

c++11已经在语言层面规范化了线程的使用。

  • 线程的任务函数返回后,子线程将终止。
  • 如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。

虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自已私有的栈空间。所以,线程结束时需要回收资源。 回收子线程的资源有两种方法:

  1. 在主程序中,调用join()成员函数等待子线程退出,回收它的资源。如果子线程已退出,join()函数立即返回,否则会产生阻塞,直到子线程退出。
  2. 在子线程中,调用detach()成员函数分离子线程,子线程退出时,系统自动回收资源。分离后的子线程不可以再join()。
int main(int argc, const char **argv) {
	thread t1(func, 1, "testing(1)...");
	thread t2(thread_f(), 2, "testing(2)...");
	t1.detach();
	t2.detach();

	cout << "begin" << endl;

	for (int i = 0; i < 5; ++i) { // 主线程要最后退出
		cout << "main work " << endl;
		Sleep(1000);
	}

	cout << "end" << endl;

	return 0;
}

用joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。

三、this_thread的全局函数

C++11提供了命名空间this_thread来表示当前线程,该命名空间中有四个函数:get_id()、sleep_for()、sleep_until()、yield()。

  1. get_id() thread::id get_id() noexcept; 该函数用于获取线程ID,thread类也有同名的成员函数。
this_thread::get_id()
t1.get_id()
  1. sleep_for()
this_thread::sleep_for(chrono::seconds(1));

通用性更强——在windows和linux下形式都一样。 3. sleep_until() 让线程休眠到一个时间点。 4. yield() 该函数让线程主动让出自已已经抢到的CPU时间片——礼让。

四、call_once函数

在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一次。在线程的任务函数中,可以用std::call_once()来保证某个函数只被调用一次。 头文件:#include<mutex>

#include <iostream>
#include <thread>
#include <windows.h>
#include <mutex>
using namespace std;

once_flag once; //声明全局变量once_flag,建立值为0和1的锁

void once_func(int n, const string &str) {
	cout << "call once func: " << n << " " << str << endl;
}

void func(int n, const string &str) {
	call_once(once, once_func, n, str);
	for (int i = 1; i < 5; ++i) {
		cout << "NO. " << n << " Loop: " << i << " " << str << endl;
		Sleep(1000);
	}
}

int main(int argc, const char **argv) {
	thread t1(func, 1, "testing(1)...");
	thread t2(func, 2, "testing(2)...");
	t1.detach();
	t2.detach();

	cout << "begin" << endl;

	for (int i = 0; i < 5; ++i) {
		cout << "main work " << endl;
		Sleep(1000);
	}

	cout << "end" << endl;

	return 0;
}

五、native_handle函数

c++11定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行封装,会损失一部分功能。为了弥补C++11线程库的不足,thread类提供了native_handle()成员函数,用于获得与操作系统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。 linux下的例子

#include <iostream>
#include <thread>
#include <pthread.h>
using namespace std;

void func() {
	for (int i = 1; i < 5; ++i) {
		cout << " Loop: " << i << endl;
		this_thread::sleep_for(chrono::seconds(1));
	}
}

int main(int argc, const char **argv) {
	thread tt(func);
	this_thread::sleep_for(chrono::seconds(5));
	pthread_t th_id = tt.native_handle(); // 获取操作系统原生的线程句柄
	pthread_cancel(th_id);
	tt.join();

	return 0;
}

六、线程同步

1. 互斥锁

C++11提供了四种互斥锁:

  • mutex:互斥锁。
  • timed_mutex:带超时机制的互斥锁。
  • recursive_mutex:递归互斥锁。
  • recursive_timed_mutex:带超时机制的递归互斥锁

包含头文件:#include<mutex>

2. mutex类

  1. 加锁lock() 互斥锁有锁定和未锁定两种状态。如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。
  2. 解锁unlock() 只有持有锁的线程才能解锁。
  3. 尝试加锁trylock() 如果互斥锁是未锁定状态,则加锁成功,函数返回true。如果互斥锁是锁定状态,则加锁失败,函数立即返回false(线程不会阻塞等待)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex ex; // 建立互斥锁,为cout的输出建立临界区。

void func() {
	for (int i = 1; i < 6; ++i) {
		ex.lock();
		cout << " Loop: " << i << endl;
		ex.unlock();
		this_thread::sleep_for(chrono::seconds(1));
	}
}

int main(int argc, const char **argv) {
	thread t1(func);
	thread t2(func);
	thread t3(func);
	thread t4(func);

	t1.join();
	t2.join();
	t3.join();
	t4.join();

	return 0;
}

3. timed_mutex类

timed_mutex tm;
tm.try_lock_for(chrono::seconds(10));

4. recursive_mutex类

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

5. lock_guard类

lock_guard是模板类,可以简化互斥锁的使用,也更安全。 lock_guard在构造函数中加锁,在析构函数中解锁。 lock_guard采用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放)。

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

mutex ex; // 建立互斥锁,为cout的输出建立临界区。

void func() {
	for (int i = 1; i < 6; ++i) {
		lock_guard < mutex > mlock(ex);
		cout << " Loop: " << i << endl;
		this_thread::sleep_for(chrono::seconds(1));
	}
}

int main(int argc, const char **argv) {
	thread t1(func);
	thread t2(func);
	thread t3(func);
	thread t4(func);

	t1.join();
	t2.join();
	t3.join();
	t4.join();

	return 0;
}