C++ 多线程(std::thread):
1、多线程使用:如下
1 std::thread t1; 2 std::thread t2(foo2); 3 t1 = std::thread(foo1); 4 t1.swap(t2); 5 t1.join(); 6 t2.join();
声明一个线程,并初始化调用的例程(函数),然后再主函数中join()线程,回收线程资源。
两个线程之间可以使用swap函数交换,在这里交换两个控制线程的变量(交换过后t1控制之前的t2,t2控制之前的t1,对运行着的线程本身并不影响)。
为什么线程要显式调用join回收资源,而不是在析构函数中隐式的调用join回收资源(像智能指针在析构函数内部释放资源)?主要是线程被create后,存在两种管理方式(或三种,第三种为cancel,thread类只能提供两种接口):join和detach,join等待线程结束,回收线程资源;detach分离线程,线程将不再受控制(detach常用于声明周期较短的线程,如果detach的线程声明周期比main中的主线程声明周期长,那么detach的线程会被强制停掉)。
如果t2的声明周期长与t1(没有swap的情况下),那么上面的代码流程是很容易理解的。若相反,t2 < t1那么上面代码使用也是正确的,t2依然可以被join(回收)。2020-06-09 16:41:50
输出当前cpu支持的线程并发数 : std::cout << std::thread::hardware_concurrency() << std::endl;
在std::this_thread中提供sleep_for函数使当前线程睡眠一段时间,get_id函数获取当前线程的id值(std::thread::id类型),yield函数让出当前线程的控制权,使线程重新调度。
std::this_thread::sleep_for(std::chrono::seconds(1)); std::thread::id id = std::this_thread::get_id();
2、线程锁的使用(mutex):如果在多线程编程中使用全局变量或是具有全局变量作用的变量做修改时,必须加锁保护。std::mutex 为普通锁,std::recursive_mutex 为递归锁(递归锁的实现仅是对普通锁属性的修改posix中)。
1 int g_cnt = 40000000; 2 std::mutex m ; 3 4 void foo(){ 5 int v_cnt = 10000000; 6 for(; v_cnt > 0; v_cnt--) 7 { 8 //std::lock_guard<std::mutex> lock(m); 9 std::unique_lock<std::mutex> lock(m); 10 //m.lock(); 11 g_cnt -- ; 12 //m.unlock(); 13 } 14 } 15 int main(){ 16 std::vector<std::thread> vector ; 17 int thread_cnt = 4; 18 for(int i = 0 ; i < thread_cnt; ++i){ 19 vector.emplace_back(foo); 20 } 21 for(auto& t : vector){ 22 t.join(); 23 } 24 25 std::cout << g_cnt << std::endl; 26 return 0; 27 }
上述代码g_cnt的输入结果一定为0,如果注释第9行,则g_cnt的输出值不一定为0,由于在修改g_cnt时不进行加锁操作则可能出现四个线程竞争情形,则g_cnt的值会大于0;第8行和第9行的作用几乎相同(任取其一就可),相当于第10行+12行,利用析构函数的优势,使lock在离开作用域使自动释放mutex上的锁,不同点时unique_lock可以再获得锁后再次调用函数释放锁,而lock_guard不行,unique_lock的接口较多,常配合condition_varible 使用。
3、条件变量:
条件变量和互斥量的使用场景不同。若有线程A,B,C,D,互斥量的作用时:在A访问某段代码时,让BCD不能访问同一段代码,或者其他会修改到A正在访问的这段代码中的数据的某段代码(四个线程角色是平等的)。而条件变量的作用,总会有一个(或多个)线程改变一个共享资源后去通知其他线程对此资源做出操作,在此过程中两方是不平等的,因为要对共享资源做出操作,因此条件变量必须配合互斥量使用。
1 std::unique_lock<std::mutex> lock(mutex); 2 cv.wait(lock,[]{return cnt ;}); 3 { 4 std::lock_guard<std::mutex> lock(mutex); 5 cnt = 1; 6 cv.notify_all(); 7 }
等待某个条件变量的线程A会含有1,2行代码。A线程执行,当cnt返回ture时,不会触发等待条件继续执行,当cnt返回false时,等待条件被触发(释放unique_lock 锁住的 mutex),A线程睡眠。
通知某个条件变量的线程B会包含3-7行类似的代码,B线程锁住mutex,改变资源cnt,然后通知多个wait的线程。当A线程接收到通知时,再次判断return cnt的值,如果为true则执行(锁住mutex),false则继续等待。
posix多线程(pthread):
1、线程基本使用:
void *start_routine(void *arg){ return ( (void*)1 ); } pthread_t thread; int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); int pthread_join(pthread_t thread, void **retval); int pthread_detach(pthread_t thread); void pthread_exit(void *retval); int pthread_cancel(pthread_t thread); pthread_t pthread_self(void);
posix线程创建由pthread_create创建,参数1为线程id,参数2为线程属性(线程栈大小,detach属性创建),参数3为线程函数(返回值和参数都为void*),参数4为线程函数参数。
posix线程创建时可以指定detach属性,如若不指定,可以在本进程任何线程内部调用pthread_detach卸离某线程。如若线程为非detach线程,则可调用pthread_join回收线程,并获得返回值retval。
posix线程三种终止方式:return返回,pthread_exit终止线程,其他线程调用pthread_cancel取消线程。线程内部若调用exit函数,整个进程会被终止,意味着所有线程都被终止。
2、线程属性:
pthread_attr_t attr; int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
使用以上函数可以设置线程创建时属性:包含detach状态,stack大小,stack最低地址,stack警戒缓冲区大小等等属性。创建线程时一般使用默认值即可,std::thread中没有线程属性设置的接口,这些接口属于底层接口,和操作系统息息相关。
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
线程的属性不仅有可以在创建时设置的,还可以在线程运行中改变线程的另外一些属性,如取消状态属性:此线程是否可以被取消,在某些系统调用时被取消,随时取消等。
3、线程同步:互斥量,条件变量,读写锁。
3.1互斥量:
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态互斥量
pthread_mutex_t mutex
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//动态创建互斥量,attr为互斥量的属性,可以设置为递归互斥量(std::recursive_mutex)
int pthread_mutex_destory(pthread_mutex_t * mutex) //销毁互斥量
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
3.2读写锁:加读锁时可以再加读锁不能再加写锁,加写锁时不能再加任何锁
pthread_rwlock_t rwlock
int pthread_rwlock_init(pthread_rwlock_t* rwlock,const pthread_rwlockattr_t* attr);
int pthread_rwlock_destrory(pthread_rwlock_t* rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
3.3条件变量:必须配合互斥量使用。
int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr);
int pthread_cond_destory(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
4、线程同步的属性:
mutex,rwlock,cond三种同步机制在创建时都可以指定属性值attr。attr的创建,设置和线程创建属性的创建和设置类似。
mutex可以设置类型(type)属性,使普通的mutex变成可递归的mutex。可设置共享(shared)属性,在多个进程共享使用,需要配合共享内存机制。
rwlock,cond 都支持共享(shared)属性,在过个进程中使用。
5、线程的私有数据:posix为每个线程创建一个errno,errno就属于每个线程的私有数据。如何创建这种私有数据?
6、线程的信号:进程中的信号是复杂的,线程模型中的信号会更复杂。pthread_sigmask(),sigwait(),pthread_kill()等。
7、线程和fork:pthread_atfork()。