线程的基本函数
1.线程创建:
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数说明:
thread:指向pthread_create类型的指针,用于引用新创建的线程。
attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。
*(*start_routine)(void *):传递新线程所要执行的函数地址。
arg:新线程所要执行的函数的参数。
调用如果成功,则返回值是0,如果失败则返回错误代码。
可以调用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数相关),但不能同时既有读者又有写者。
本文出自 “11408774” 博客,请务必保留此出处http://11418774.blog.51cto.com/11408774/1840637
原文地址:http://11418774.blog.51cto.com/11408774/1840637