线程的基本函数 多线程
1.线程建立:并发
#include <pthread.h>ide
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);函数
参数说明:高并发
thread:指向pthread_create类型的指针,用于引用新建立的线程。post
attr:用于设置线程的属性,通常不须要特殊的属性,因此能够简单地设置为NULL。spa
*(*start_routine)(void *):传递新线程所要执行的函数地址。线程
arg:新线程所要执行的函数的参数。指针
调用若是成功,则返回值是0,若是失败则返回错误代码。orm
能够调用pthread_self( )获取当前线程的id
2.线程终止
若是须要只终止某个线程而不终止整个进程,能够有三种方法:
从线程函数return。这种方法对主线程不适用,从main函数return至关于调用exit。
一个线程能够调用pthread_cancel终止同一进程中的另外一个线程。
线程能够调用pthread_exit终止本身。
#include <pthread.h>
void pthread_exit(void *retval);
参数说明:
retval:返回指针,指向线程向要返回的某个对象。
线程经过调用pthread_exit函数终止执行,并返回一个指向某对象的指针。注意:毫不能用它返回一个指向局部变量的指针,由于线程调用该函数后,这个局部变量就不存在了,这将引发严重的程序漏洞。
3.线程等待
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
参数说明:
th:将要等待的张璐,线程经过pthread_create返回的标识符来指定。
thread_return:一个指针,指向另外一个指针,然后者指向线程的返回值。
有关分离线程
在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程可以被其余线程收回其资源和杀死。在被其余线程回收以前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其余线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
默认状况下,线程被建立成可结合的。为了不存储器泄漏,每一个可结合线程都应该要么被显示地回收,即调用pthread_join;要么经过调用pthread_detach函数被分离。若是一个可结合线程结束运行但没有被join,则它的状态相似于进程中的Zombie Process,即还有一部分资源没有被回收,因此建立线程者应该调用pthread_join来等待线程运行结束,并可获得线程的退出代码,回收其资源。因为调用pthread_join后,若是该线程没有运行结束,调用者会被阻塞。
线程同步与互斥
A.mutex (互斥量)
原子操做:要么都执行,要么都不执行
对于多线程的程序,访问冲突的问题是很广泛的,解决的办法是引入互斥锁(Mutex,MutualExclusiveLock),得到锁的线程能够完成“读-修改-写”的操做,而后释放锁给其它线程,没有得到锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操做组成一个原子操做,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行作这个操做。Mutex用pthread_mutex_t类型的变量表示,能够这样初始化和销毁:
返回值:成功返回0,失败返回错误号。
pthread_mutex_init函数对Mutex作初始化,参数attr设定Mutex的属性,若是attr为NULL则表示缺省属性,本章不详细介绍Mutex属性,感兴趣的读者能够参考[APUE2e]。用pthread_mutex_init函数初始化的Mutex能够用pthread_mutex_destroy销毁。若是Mutex变量是静态分配的(全局变量或static变量),也能够用宏定义PTHREAD_MUTEX_INITIALIZER操做能够用下列函数:
返回值:成功返回0,失败返回错误号。
一个线程能够调用pthread_mutex_lock得到Mutex,若是这时另外一个线程已经调用pthread_mutex_lock得到了该Mutex,则当前线程须要挂起等待,直到另外一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能得到该Mutex并继续执行。若是一个线程既想得到锁,又不想挂起等待,能够调用pthread_mutex_trylock,若是Mutex已经被 另外一个线程得到,这个函数会失败返回EBUSY,而不会使线程挂起等待。
通常状况下,若是同一个线程前后两次调用lock,在第二次调用时,因为锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被本身占用着的,该线程又被挂起而没有机会释放锁,
所以就永远处于挂起等待状态了,这叫作死锁(Deadlock)。另外一种典型的死锁情形是这样:线程A得到了锁1,线程B得到了锁2,这时线程A调用lock试图得到锁2,结果是须要挂起等待线程B释放锁2,而这时线程B也调用lock试图得到锁1,结果是须要挂起等待线程A释放锁1,因而线程A和B都永远处于挂起状态了。不难想象,若是涉及到更多的线程和更多的锁,有没有可能死锁的问题将会变得复杂和难以判断。
B. Condition Variable(条件变量)
返回值:成功返回0,失败返回错误号。
和Mutex的初始化和销毁相似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。若是ConditionVariable是静态分配的,也能够用宏定义PTHEAD_COND_INITIALIZER初始化,至关于用pthread_cond_init函数初始化而且attr参数为NULL。Condition Variable的操做能够用下列函数:
返回值:成功返回0,失败返回错误号。
可见,一个Condition Variable老是和一个Mutex搭配使用的。一个线程能够调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数作如下三步操做:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,从新得到Mutex并返回
c. Semaphore(信号量)
Mutex变量是非0即1的,可看做一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时得到该资源,将Mutex减到0,表示再也不有可用资源,解锁时释放该资源,将Mutex从新加到1,表示又有了一个可用资源。semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程间同步,这里只介绍这种状况。在用完semaphore变量以后应该调用sem_destroy()释放与semaphore相关的资源。
调用sem_wait()能够得到资源(P操做),使semaphore的值减1,若是调用sem_wait()时semaphore的值已是0,则挂起等待。若是不但愿挂起等待,能够调用sem_trywait()。调用sem_post()能够释放资源(V操做),使semaphore的值加1,同时唤醒挂起等待的线程。
示例:
生产者-消费者模型,基于固定大小的环形队
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <signal.h> #include <string.h> #include <semaphore.h> #define _SEM_PRO_ 64 #define _SEM_CON_ 0 sem_t sem_product; sem_t sem_consume; pthread_mutex_t con_lock; pthread_mutex_t pro_lock; int bank[_SEM_PRO_]; void* producter(void* _val) { int index_pro = 0; while(1) { sem_wait(&sem_product); pthread_mutex_lock(&pro_lock); int _product = rand()%1000; bank[index_pro] = _product; printf("product1 done...val is : %d \n", _product); pthread_mutex_unlock(&pro_lock); sem_post(&sem_consume); ++index_pro; index_pro = index_pro%_SEM_PRO_; //sleep(1); } } void* producter2(void* _val) { int index_pro = 0; while(1) { sem_wait(&sem_product); pthread_mutex_lock(&pro_lock); int _product = rand()%1000; bank[index_pro] = _product; printf("product2 done...val is : %d \n", _product); pthread_mutex_unlock(&pro_lock); sem_post(&sem_consume); ++index_pro; index_pro = index_pro%_SEM_PRO_; //sleep(1); } } void* consumer(void* _val) { int index_con = 0; while(1) { sem_wait(&sem_consume); pthread_mutex_lock(&con_lock); int _consume = bank[index_con]; printf("consume1 done...val is : %d\n", _consume); pthread_mutex_unlock(&con_lock); sem_post(&sem_product); ++index_con; index_con = index_con%_SEM_PRO_; //sleep(1); } } void* consumer2(void* _val) { int index_con = 0; while(1) { sem_wait(&sem_consume); pthread_mutex_lock(&con_lock); int _consume = bank[index_con]; printf("consume2 done...val is : %d\n", _consume); pthread_mutex_unlock(&con_lock); sem_post(&sem_product); ++index_con; index_con = index_con%_SEM_PRO_; //sleep(1); } } void run_product_consume() { pthread_t tid_pro; pthread_t tid_con; pthread_t tid_con2; pthread_t tid_pro2; pthread_create(&tid_pro, NULL, producter, NULL); pthread_create(&tid_con, NULL, consumer, NULL); pthread_create(&tid_pro2, NULL, producter2, NULL); pthread_create(&tid_con2, NULL, consumer2, NULL); pthread_join(tid_pro, NULL); pthread_join(tid_con, NULL); pthread_join(tid_pro2, NULL); pthread_join(tid_con2, NULL); } void destroy_all_sem() { printf("process done...\n"); sem_destroy(&sem_product); sem_destroy(&sem_consume); pthread_mutex_destroy(&con_lock); pthread_mutex_destroy(&pro_lock); exit(0); } void init_all_sem() { signal(2, destroy_all_sem); memset(bank, 0, sizeof(bank)); sem_init(&sem_product, 0, _SEM_PRO_); sem_init(&sem_consume, 0, _SEM_CON_); pthread_mutex_init(&con_lock, NULL); pthread_mutex_init(&pro_lock, NULL); } int main() { init_all_sem(); run_product_consume(); return 0; }
d. 读写锁
在编写多线程的时候,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。一般而言,在读的过程当中,每每伴随着查找的操做,中间耗时很长。给这种代码段加锁,会极大地下降咱们程序的效率。所以,读写锁有派上了用场。
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分红读者和写者,读者只对共享资源进行读访问,写者则须要对共享资源进行写操做。这种锁相对于自旋锁而言,能提升并发性,由于在多处理器系统中,它容许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。