现在的位置: 首页 > 综合 > 正文

Posix线程基础

2013年10月19日 ⁄ 综合 ⁄ 共 5516字 ⁄ 字号 评论关闭

 POSIX


线程


基础



 在传统的UNIX


进程模型中,每个进程只有一个控制线程,或者也可以说一个进程只有一个线程,实质上在这个地方进程就等同于线程。在POSIX线程中,程序运行时它也是由单进程中的单个控制线程启动的,在创建多个线程之前,程序行为和传统的进程没有什么区别。

一、线程的创建和消亡
1、线程创建 :
#include<pthread.h>
int pthread_create(pthread_t *restrict tidp,
                                    const pthread_attr_t * restrict attr,
                                    void *(*start_rtn)(void), void * restrict arg) ;
函数第一个参数是线程ID,线程ID在不同的类unix系统(比如说linux


,FreeBSD)实现是不一样的,有些是结构,有些是无符号整形,所以不能简答的通过相等来判断两个线程ID是否同一个线程;
获得线程ID:
#include<pthread.h>
pthread_t pthread_self(void) ;
判断是否同一线程:
#include<pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2) ;
函数第二个参数是线程的属性,具体的属性在这里不讨论,一般设置


成NULL,选择默认属性;
函数第三个参数是线程要执行函数的入口地址,就是一个函数指针,这个函数的指针具有特定的返回值和参数即void * 和 void ;
函数第四个参数是传递给函数指针的参数,如果需要传递多个的话arg就必须是一个结构体。

新线程可以访问进程的地址空间


,主线程在创建一个线程之后并不能保证哪个线程先执行,哪个进程后执行,用Rich Stevens的话来说就是:“永远不要猜测操作系统


是用一种什么方式来调度进程的”,而且一旦主线程退出,其他线程都会被强制退出,因此必须存在一个线程之间的竞争处理机制。
[在这里插上一小段内容,在unix环境编程中提到linux实际上是采用了fork的方式来间接的实现了线程的概念,因此在打印同一进程里面线程的进程ID的时候是不一样的,但是我测试了下,发现进程ID是一样的。]

2、线程终止
进程中的任意线程调用了exit,_Exit,或者_exit整个进程都将会终止,单个线程可以通过三种方式来:
线程在启动例程中返回,返回值就是线程的退出码(return) ;
线程可以被同一进程中的其他线程取消 ;
线程自身调用pthread_exit ;
线程直接返回就不用说了,同一进程中的线程要取消其他线程可以通过调用函数:
#include<pthread.h>
int pthread_cancel(phtread_t tid) ;
由被取消的线程决定是否决定退出,它也可以忽略这个消息,线程调用这个函数以后并不会等待线程终止 ;
线程自身调用pthread_exit:
#include<pthread.h>
void pthread_exit(void * rval_ptr) ;
这个函数具有一个参数,它相当于直接return的返回值,其他线程可以通过函数pthread_join来获得这个参数:
#include<pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr) ;

个进程调用pthread_join来等待另一个进程的结束并取得其返回值,如果对返回值没兴趣的话可以设置为NULL。phread_join会使等待
的进程处在分离状态(即线程结束以后可以自动的释放资源),如果一个线程已经处于分离状态(可以通过属性设置来创建分离状态的线程),则此时
pthread_join就会返回错误。
在这里要注意一个,线程调用pthread_exit返回时候返回值必须在进程退出以后还是有效的,因为如果是在线程本身的栈声明的临时变量,则线程退出以后这个栈的数据


可能就不存在另做他用了。

线程可以决定自己退出时候需要调用的函数,类似于析构函数吧:
#include<pthread.h>
void pthread_cleanup_push(void(*rtn)(void *), void * arg) ;
void pthread_cleanup_pop(int execute) ;
这两个函数必须成对出现,即有一个push就必须有一个相对应的pop。可以设置多个需要调用的函数,最后这些函数的执行顺序是根据push的顺序来指定的。在以下情况下线程退出会执行push这定的函数:
调用pthread_exit的时候 ;
响应取消请求的时候 ;
用非0的execute参数调用phread_cleanup_pop的时候, 注意不管采用0还是非0参数执行这个函数,都会从队列中删除上次pthread_cleanup_push建立的清理程序,即不管执行与不执行,都要将当前队列中轮询到的函数从队列中移出;

二、线程的同步
当多个线程共享相同的内存的时候,必须保证所有线程看到的都是一致的数据视图,也就是必须保证对共享数据的原子操作,线程之间的同步有三种基本方式:
1、互斥量(mutex)

过线程之间的互斥来保护数据,确保同一时间只会有一个线程来访问数据。互斥量实际上是一把锁,它不是用来锁数据的,而是锁互斥量本身,每个线程在访问共享
数据之前都对这个互斥量进行加锁,加锁成功的话就可以独占地访问数据,如果加锁过程已经互斥量已经被锁住了,则它就必须等待直到其他线程释放这个互斥量,
当有多个线程访问等待同一个互斥锁的时候,第一个变为运行状态的线程获得互斥锁,其他的则继续等待。
互斥量用pthread_mutex_t来表示,使用之前必须初始化,可以静态分配其为常量,也可以动态分配其值,如果是动态分配,使用完后必须释放。
#include<pthread.h>
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER ; //静态分配常量
int pthread_mutex_init(pthread_mutex_t * restrict mutex,
                                            contst pthread_mutexattr_t * restrict attr) ;
int pthread_mutex_destroy(pthread_mutex_t * mutex) ;
动态初始化的后一个参数是用来设置mutex的属性,在这里我们使用默认属性NULL。具体属性可以去查spec(有递归锁等各种性质的互斥量)。
对互斥量进行加锁的过程中如果互斥量已经上锁,则调用线程就会阻塞直到互斥量被解锁:
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t * mutex) ;
int pthread_mutex_trylock(pthread_mutex_t * mutex) ;
int pthread_mutex_unlock(pthread_mutex_t * mutex) ;
第一个是锁,锁不了就会等待;第二个是试图锁,如果锁成功则锁住,如果未成功不会等待并返回0;
使用互斥量必须注意防止死锁,比如说同一个线程试图对同一个互斥量加锁多次等等。可以同时使用多个互斥量来控制对数据的访问,但要注意如果锁的粒度太粗就会出现很多线程等待同一个锁,如果锁的粒度太细那么过多的锁开销也会影响性能。

2、读写锁
读写锁在我看到的代码中好像使用得不是很多,读写锁分为两种锁状态,一个是读状态加锁一个是写状态加锁。读状态加锁是共享访问,写状态加锁就是独占访问,一次可有多个线程加锁读状态模式,但是只有一个线程能加锁写状态模式。

果读写锁是写加锁状态时,所有企图对读写锁进行加锁的操作都会阻塞;如果读写锁是读加锁状态时候,所有试图对读模式进行加锁的线程就能获得访问权,此时如
果再来一个线程请求进行写模式加锁,那么一般就会阻塞以后的读模式请求,以防止写模式过长时间的等待。读写锁主要适用于那些读远远大于写的数据结构。
读写锁只有一种初始化方式,动态方式:
#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t * restrict rwlock,
                                              const pthread_rwlockattr_t * restrict attr) ;
int pthread_rwlock_destroy(pthread_rwlock_t * rwlock) ;
加锁方式:
#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock) ;
int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock) ;
int pthread_rwlock_unlock(pthread_rwlock_t * rwlock) ;
int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock) ;
int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock) ;

3、条件变量
条件变量和mutex一样也是用得比较多的一种同步机制。条件变量和互斥量一起使用时允许线程之间的竞争在特定的条件下发生。
条件变量和mutex类似也是两种初始化方式:
#include<pthread.h>
pthread_cond_t mlock = PTHREAD_COND_INITIALIZER ;
int pthread_cond_init(pthread_cond_t * restrict cond,
                                          pthread_condattr_t * restrict attr) ;
int pthread_cond_destroy(pthread_cond_t * cond) ;
这里的cond只是用来在等待和唤醒之间传递信号的一个变量,真正的条件不是它。条件本身是由互斥量进行保护的,在判断或者改变条件之前首先要锁住互斥量。
等待条件变量:
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t * restrict cond ,
                                          pthread_mutex_t * restrict mutex) ;
int pthread_cond_timewait(pthread_cond_t * restrict cond,
                                                  pthread_mutex_t * restrict mutex ,
                                                  const struct timespec * restrict timeout) ;
一个等待一个超时等待,超时等待的时间不是相对时间而是一个绝对时间。
唤醒等待的线程:
#include<pthread.h>
int pthread_cond_singal(pthread_cond_t * cond) ;
int pthread_cond_broadcast(pthread_cond_t * cond) ;
第一个唤醒一个等待的线程(有多个等待的时候是否随机唤醒?),第二个唤醒所有等待的线程,但实际上只有一个会运行。
现在说明一下pthread_cond_wait的原理


,这个函数首先会把当前进程放入等待队列,然后会把mutex解锁,这样就可以等待条件的改变,当这个函数返回的时候它会把mutex重新加锁,这也是为什么多个线程同时被唤醒时实际只有一个运行其他的会因为mutex加锁而继续等待。可以看下面的例子:
24 void * increace(void * arg){
 25     pthread_mutex_lock(&mutex) ;
 26     cout <<"before increace, NUM = "<< num<<endl ;
 27     if(num%2 != 0)
 28         pthread_cond_wait(&cond, &mutex) ;
 29     num++ ;
 30     cout <<"after increace, NUM = "<< num<<endl ;
 31     pthread_mutex_unlock(&mutex) ;
 32 }
 33 void * decreace(void * arg){
 34     pthread_mutex_lock(&mutex) ;
 35     cout <<"before decreace, NUM = "<< num<<endl ;
 36     sleep(3) ;
 37     num--;
 38     if(num%2 == 0)
 39         pthread_cond_signal(&cond) ;
 40     num-- ;
 41     cout <<"after decreace, NUM = "<< num<<endl ;
 42     pthread_mutex_unlock(&mutex) ;
 43}

抱歉!评论已关闭.