C++11多线程
- “语言级别”→ 跨平台 - windows/linux/max
- “语言层面”→ thread -> windows - createThread; linux - pthread_create(实质调用的仍是各系统的接口)
基本用法
- 如何创建启动一个线程 -> 传入线程所需的线程函数和参数,线程自动开启std::thread t(threadFunction);
- 子线程如何结束 -> 子线程函数在执行完成,线程就结束了
- 主线程如何处理子线程 -> (1)t.join():等待t线程结束,当前线程继续往下运行;(2)t.detach():将t线程设置为分离线程,主线程结束,整个线程结束,所有的子线程都自动结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <thread> #include <chrono>
using namespace std;
void download() { this_thread::sleep_for(chrono::milliseconds(500)); cout << "child thread: " << this_thread::get_id() << ", download success...." << endl; }
void func(int num, string str) { cout << "child thread: " << this_thread::get_id << endl; cout << "num: " << num << ", str: " << str << endl; }
int main() { cout << "main thread ID: " << this_thread::get_id() << endl;
thread t(download);
t.join();
thread t1(func, 13, "lucky number");
t1.detach();
this_thread::sleep_for(chrono::seconds(5));
int num = thread::hardware_concurrency(); cout << "CPU number: " << num << endl;
return 0; }
|
临界区互斥锁
- 多线程程序执行的结果是一致的,不会随着CPU对线程的不同调用顺序而产生不同的运行结果
- 若多线程程序会产生不同的运行结果,则称为竞态条件
- 当多个线程均涉及到临界区资源的修改时,可用mutex对临界区资源进行加锁解锁,以保证程序正常执行!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include <thread> #include <mutex> #include <list> using namespace std;
int ticketCount = 100; std::mutex mtx;
void sellTicket(int index) { while (ticketCount > 0) { mtx.lock(); if (ticketCount > 0) { cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl; ticketCount--; } mtx.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }
int main() { list<std::thread> tlist; for (int i = 0; i < 3; ++i) { tlist.push_back(std::thread(sellTicket, i)); }
for (std::thread& t : tlist) { t.join(); }
cout << "所有窗口卖票结束..." << endl; return 0; }
|
- 一般加锁,若程序在临界区中return,则可能无法解锁,造成死锁的问题。lock_guard是在作用域中加锁,而出作用域会自动解锁,若临界区中return相当于出了作用域,故会自动解锁,不会因return造成死锁问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void sellTicket(int index) { while (ticketCount > 0) { { lock_guard<std::mutex> lock(mtx); if (ticketCount > 0) { cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl; ticketCount--; } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }
|
- lock_guard定义处会调用构造函数加锁,离开定义域的话lock_guard会被销毁,调用析构函数解锁 → 若作用域较大,则锁的颗粒度较大,很大程度上影响效率。通信中常用unique_lock!
- 一般加锁、解锁不涉及return时,可以考虑使用mutex!
- lock_guard不可能用在函数参数传递或返回过程中,只能用于简单代码段的互斥操作中!
- unique_lock不仅可以用于简单代码段的互斥操作,亦可用在函数的调用过程中!
同步通信
多线程编有两个问题:
- 线程间的互斥(竞态条件 -> 临界区代码段“加锁”)
- 线程间的同步通信 -> 生产者、消费者模型
若生产者还未生产“产品”,消费者无法消费 -> 故需进行线程同步(“线程间通话”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> #include <chrono>
using namespace std;
mutex mtx; condition_variable cv;
class Queue { public: Queue() : max_size(5) {}
void put(int val) { unique_lock<mutex> lck(mtx); while (que.size() >= max_size) { cv.wait(lck); } que.push(val);
cv.notify_all(); cout << "生产者生产了:" << val << " 号物品" << endl; }
int get() { unique_lock<mutex> lck(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; int max_size; };
void producer(Queue* que) { for (int i = 1; i <= 10; ++i) { que->put(i); this_thread::sleep_for(chrono::milliseconds(100)); } }
void consumer(Queue* que) { for (int i = 1; i <= 10; ++i) { que->get(); this_thread::sleep_for(chrono::milliseconds(100)); } }
int main() { Queue que;
thread t1(producer, &que); thread t2(consumer, &que);
t1.join(); t2.join();
return 0; }
|
- cv.notify_all() -> 通知其它在cv上等待的线程
- 其它在cv上等待的线程一旦收到通知:等待状态 -> 阻塞状态 -> 获取互斥锁 -> 继续执行程序!
无锁队列CAS
- 若临界区代码段较大,用互斥锁可能会比较“重”,不利于效率提升
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <thread> #include <mutex> #include <atomic> #include <list> #include <chrono> using namespace std;
volatile std::atomic_bool isReady = false; volatile std::atomic_int m_count = 0;
void task() { while (!isReady) { std::this_thread::yield(); }
for (int i = 0; i < 100; ++i) { m_count++; } }
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(3)); isReady = true;
for (std::thread& t : tlist) { t.join(); } cout << "m_count:" << m_count << endl;
return 0; }
|
- 多线程缓存可以加快线程运行的效率
- volatile -> 无需多线程缓存共享变量 -> 某线程修改共享变量后及时反应!
Linux多线程
线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。可理解为:进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。
基本函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <pthread.h>
pthread_t pthread_self(void);
int pthread_create(pthread_t *thread, const pthread_attrt_t *attr, void *(*start_routine)(void *), void *arg));
void pthread_exit(void *retval);
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t thread);
|
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<pthread.h>
void* callback(void* arg) { printf("子线程:%ld\n", pthread_self());
int *a = (int*) arg; *a = 100;
pthread_exit(&a);
return NULL; }
int main() { pthread_t tid;
int a;
pthread_create(&tid, NULL, callback, &a);
printf("主线程:%ld\n", pthread_self());
pthread_join(tid, NULL);
printf("a = %d\n", a);
pthread_exit(NULL); return 0; }
|
线程同步的4种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <pthread.h>
pthread_mutex_t mutex;
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_rwlock_t rwlock;
pthread_cond_t cond;
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_signal(pthread_cond_t *cond);
#include<semaphore.h> sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem); int sem_post(sem_t *sem);
|