一、一些概念
1、在Pthreads程序中,全局变量被所有线程所共享,而在函数中声明的局部变量则(..通常)由执行该函数的线程所私有。
2、临界区:一个更新共享资源的代码段,一次只允许一个线程执行该代码段。
3、竞争条件:当多个线程都要访问共享变量或者共享文件这样的共享资源时,如果其中一个
访问是更新操作,那么这些访问就可能会导致某种错误,我们称之为竞争条件。
4、互斥量:是互斥锁的简称。是一个特殊类型的变量,通过某些特殊类型的函数,互斥量可
以用来限制每次只有一个线程能够进入临界区。互斥量保证了一个线程独享临界区,其他线
程在有线程已经进入临界区的情况下不能同时进入。
互斥量的特殊类型为:pthread_mutex_t。在使用该类型变量之前必须有系统对其进行初始
化。
5、生产者—消费者同步模型:一个线程需要等待另一个线程执行某种操作的同步方式。
6、路障:通过保证所有线程在程序中处于同一个位置来同步线程,这个同步点称为路障,只
有所有线程都抵达此路障,线程才能继续运行下去,否则会阻塞在路障处。
7、条件变量:是一个数据对象,允许线程在某个特定条件或时间发生前都处于挂起状态,当
时间或条件发生时,另一个线程可以通过信号来唤醒挂起的线程。一个条件变量总是与一个
互斥量相关联。
8、Pthreads线程库中的条件变量类型为Pthread_cond_t。
9、读写锁
·提供了两个锁函数,第一个为读操作对读写锁加锁,第二个为写操作对读写锁加锁。
·多个线程能够通过调用读锁函数而同时获得锁,但只有一个线程能通过写锁函数获得
锁。
·任何线程拥有了读锁,则任何请求写锁的线程将被阻塞在写锁函数的调用上。
·任何线程拥有了写锁,则任何想获取读锁或写锁的线程将阻塞在他们对应的锁函数上。
10、缓存行(块)
如果一个处理器需要访问主存位置x,那么就不只是将x的内容传入(出)主存,而是将
一块包含x的内存块传入(出)主存,这样的一块内存称为缓存行(块)。
二、Pthreads编程代码
编译程序
$ gcc -g -Wall -o 文件名 文件名.c -lpthread
运行程序
$ ./文件名 <number of thread>
//number of thread 是可选项,表示要启动的线程的个数
将字符串转换为long int(长整型)
long strtol(const* char number_p /*in*/,char** end_p /*out*/,int base /*in*/);
//number_p是待转换的字符串
//base是表达这个整数值所用的基
//如果end_p不是NULL,它就指向number_p字符串中第一个无效字符(非数值字符)
生成线程的函数
int pthread_create(pthread_t* thread_p /*out*/,const pthread_attr_t* attr_p /*in*/,
void* (*start_routine)(void*) /*in*/,void* arg_p /*in*/);
//thread_p是一个指针,指向对应的pthread_t对象,她不是由pthread_create函数分配的,必须在调用pthread_create函数前将pthread_t对象分配到内存空间。
//attr_p不用,只是在函数调用时把NULL传递给参数
//(*start_routine)(void*)表示该线程将要运行的函数
//arg_p是一个指针,指向传递给start_routine的参数
//返回值一般用于表示线程调用过程中是否有错误
停止线程
int pthread_join(pthread_t thread /*in*/,void** ret_val-p /*out*/);
//thread指向对应的pthread_t对象
//ret_val_p可以接收任意有pthrea_t对象所关联的那个线程产生的返回值
互斥量初始化函数
int pthread_mutex_init(pthread_mutex_t* mutex_p /*out*/,
const pthread_mutexattr* attr_p /*in*/);
//第二个参数不使用,赋值为NULL即可
使用完互斥量之后调用的函数
int pthread_mutex_destroy(pthread_mutex_t* mutex_p /*in/out*/);
获得临界区访问时调用的函数
int pthread_mutex_lock(pthread_mutex_t* mutex_p /*in/out*/);
//调用该函数会使线程等待,直到没有其他线程进入临界区
线程退出临界区后调用的函数
int pthread_mutex_unlock(pthread_mutex_t* mutex_p /*in/out*/);
//调用该函数会通知系统该线程已经完成了临界区中代码的执行
不同信号量函数的语法:信号量不是Pthreads线程库的一部分,所以需要在使用信号量的程
序开头加头文件#include<semaphore.h>
int sem_init(sem_t* semaphore_p /*out*/,int shared /*in*/,unsigned initial_val /*in*/);
//一般不使用该函数的shared参数,对这个参数只需要传入常数0就可以
int sem_destroy(sem_t* semaphore_p /*in/out*/);//销毁
int sem_post(sem_t* semaphore_p /*in/out*/);//解锁
int sem_wait(sem_t* semaphore_p /*in/out*/);//阻塞
条件变量的一般使用方法的伪代码:
lock mutex;
if condition has occurred
signal thread(s);
else {
unlock the mutex and block;/*when thread is unblocked,mutex is relocked*/
}
unlock mutex;
解锁一个阻塞的线程的函数
int pthread_cond_signal(pthread_cont_t* cont_var_p /*in/out*/);
解锁所有被阻塞的线程的函数
int pthread_cond_broadcast(pthread_cond_t* cond_var_p /*in/out*/);
通过互斥量mutex_p来阻塞线程的函数,被阻塞线程想要解锁只有其他线程调用
pthread_cond_signal或者pthread_cond_broadcast来解锁。当线程解锁后,它重新获得互斥量。
int pthread_cond_wait(pthread_cond_t* cond_var_p /*in/out*/,
pthread_mutex_t* mutex /*in/out*/);
//该函数相当于按顺序执行了以下函数:
pthread_mutex_unlock(&mutex_p);
wait_on_signal(&cond_var_p);
pthread_mutex_lock(&mutex_p);
用条件变量实现路障
/*Shared*/
int counter=0; //共享计数器
pthread_mutex_t mutex; //互斥量
pthread_cond_t cond_var; //条件变量
...
void* Thread_work(...){
...
/*Barrier*/
pthread_mutex_lock(&mutex); //线程获得临界区的访问权
counter++; //共享计数器加1,用来计数使用过临界区的线程的个数
if(counter==thread_count){ //满足了共享计数器的值等于线程个数的条件
counter==0; //共享计数器归零开始下一次计数
pthread_cond_broadcast(&cond_var); //解锁所有被阻塞的线程
}else{
while(pthread_cond_wait(&cond_var,&mutex)!=0); //通过互斥量来阻塞线程
}
pthread_mutex_unlock(&mutex); //通知系统该线程已经已经完成了临界区中代码的执行
...
}
//注意除了调用pthread_cond_broadcast函数外,其他的某些事件也可能将挂起的线程解锁,因此,函数
//pthread_cond_wait一般被放置于while循环内,如果线程不是被pthread_cond_broadcast或
//pthread_cond_signal函数解锁而是被其他事件解除阻塞,那么能检查到pthread_cond_wait函数的返回值不
//为0,被解除阻塞的线程还会再次执行该函数
条件变量初始化函数
int pthread_cond_init(pthread_cont_t* cond_p /*out*/,
const pthread_condattr_t* cond_attr_p /*in*/);
//cond_attr_p不使用,调用函数时传递NULL作为参数值
条件变量销毁函数
int pthread_cond_destroy(pthread_cond_t* cond_p /*in/out*/);
用每个链表结点拥有一个互斥量的方法来实现有序链表的Member函数
struct list_node_s{ //链表结点结构
int data;
struct list_node_s* next;
pthread_mutex_t mutex; //每个链表结点的互斥量
}
int Member(int value){
struct list_node_s* temp_p; //定义临时链表
pthread_mutex_lock(&head_p_mutex); //获得头结点的使用权
temp_p=head_p;
while(temp_p->next!=NULL&&temp_p->data<value){
//遍历寻找要访问的值,因为是有序链表,所以条件可以为temp_p->data<value
if(temp_p->next!=NULL)
pthread_mutex_lock(&(temp_p->next->mutex)); //获得当前节点的下一节点的使用权
if(temp_p==head_p)
pthread_mutex_unlock(&head_p_mutex);
//如果当前节点是头结点释放头结点的使用权,满足条件进入该循环必然会进行这一步
pthread_mutex_unlock(&(temp_p->mutex)); //释放当前节点的使用权,便于其他线程使用
temp_p=temp_p->next; //当前节点后移一位
}
if(temp_p==NULL||temp_p->data>value){ //查找失败的处理
//当前节点为尾结点或者节点的值大于要找的值都表示匹配失败
if(temp_p==head_p)
pthread_mutex_unlock(&head_p_mutex);
//如果当前节点是头结点(即该链表为空)释放头结点的使用权
if(temp_p!=UNLL)
pthread_mutex_unlock(&(temp_p->mutex)); //虽然查找失败还是需要释放使用权
return 0;
}else{ //查找成功的处理
if(temp_p==head_p)
pthread_mutex_unlock(&head_p_mutex);
//如果当前节点是头结点(头结点的值就是要找的值)释放头结点的使用权
pthread_mutex_unlock(&(temp_p->mutex)); //查找成功后也需要释放使用权
return 1;
}
}
//该程序看似会有多次的获取使用权与多次的释放使用权,其实只有两次该过程的出现,一次是对头结点的操
//作,一次是对查找结点的操作
//对于头结点,若满足循环条件则会在循环中释放使用权
//若不满足循环条件则头结点的释放会放在后面进行
读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock_p /*in/out*/); //读加锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock_p /*in/out*/); //写加锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock_p /*in/out*/); //解锁
int pthread_rwlock_init(pthread_rwlock_t* rwlock_p /*out*/,
const pthread_rwlockattr_t* attr_p /*in*/); //初始化
//第二个参数不使用,调用时传递NULL
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock_p /*in/out*/); //释放读写锁