关于Linux多线程编程
Linux线程分为两类,一是核心级支持线程,在核心级实现线程时,线程的实现依赖于内核,无论是在用户进程中的线程还是系统进程中的线程,他们的创建、撤消、切换都由内核实现。核心只有单线程进程概念,而多线程进程由与应用程序连接的过程库实现。另一类线程是用户级线程,在Linux众多的线程库中,大部分实现的是用户级线程。
系统创建线程的顺序如下:当一个线程启动后,它会自动创建一个线程即主线程(main thread)或者初始化线程(initial thread),然后就利用pthread_initialize()初始化系统管理线程并且启动线程机制。
Linux线程编程基础
要创建一个多线程程序,必须加载pthread.h头文件。要掌握多线程编程常用的几个函数:
1、创建新线程函数:        pthread_create()
2、挂起当前线程函数:      pthread_join()
3、线程注册的清除处理函数:pthread_exit()
4、取消一个线程函数:      pthread_cancel()
5、挂起当前线程,直到满足某种条件: pthread_cond_init
多线程的同步
1、互斥锁
互斥锁用来保证一段时间内只有一个线程在执行一段代码。当在同一内存空间运行多个线程时,为保证多个线程之间不相互破坏,要创建互斥量,如果一个线程已经锁定一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。第二个线程将被唤醒并继续执行,同时锁定这个互斥量。
创建互斥量时,必须首先声明一个类型为pthread_mutex_t的变量,然后对其进行初始化,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。
锁定一个互斥量时使用函数pthread_mutex_lock(),它尝试锁定一个互斥量,如果该互斥量已经被其它线程锁定,该函数就把调用自己的线程挂起,一旦该互斥量解锁,它将恢复运行并锁定该互斥量。这个线程在做完它的事情后,必须释放这个互斥量,解除锁定时使用函数pthread_mutex_unlock()。
用完一个互斥量后必须销毁它,这时没有任何线程再需要它了,最后一个使用该互斥量的线程必须销毁它,销毁互斥量时使用函数pthread_mutex_destroy().
2、条件变量
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。在某种情况下,例如,在图形用户界面程序中,一个线程读取用户输入,另一个线程处理图形输出,第三个线程发送请求到服务器并处理其响应,当服务器的响应到达时,处理服务器的线程必须可以通知画图形的线程,画图形的线程把相应的结果显示给用户。管理用户输入的线程必须总是能响应用户,例如,允许用户取消正在由处理服务器的线程执行的耗时的操作,这表明线程间必须可以互相传递信息,这时需要引入条件变量。
条件变量是一种可以使线程(不消耗CPU)等待某些事件发生的机制。某些线程可能守候着一个条件变量,直到某个其它的线程给这个条件变量发送一个信号,这时这些线程中的一个线程就会苏醒,处理这个事件。也有可能利用对条件变量的广播唤醒所有守侯着这个条件变量的线程。但条件变量不提供锁定,所以它必须与一个互斥量同时使用,提供访问这个环境变量时必要的锁定。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁,并等待条件发送变化。一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥量,并重新测试条件是否满足。一般来说,条件变量被用来进行线程间的同步。
下面通过一个例子来介绍条件变量:
pthread_mutex_t  count_mutex;
pthread_cond_t  cont_nonzero;
unsigned int count;
union是什么类型decrement_count() {
  pthread_mutex_lock  (&count_mutex);
  while(count= =0)
  pthread_cond_wait (&count_nonzero, & count_mutex);
  count=count –1;
pthread_mutex_unlock  (&count_mutex);
}
increment_count(){
  pthread_mutex_lock(&count_mutex);
  if(count= =0)
pthead_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_mutex);
}
条件变量的结构为pthread_cond_t,创建条件变量时必须首先声明一个类型为pthread_cond_t的变量,然后对它进行初始化。初始化可以如下所示:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
但是,由于PTHREAD_COND_INITIALIZER是一个结构,所以只能在条件变量声明时对它进行初始化,在运行时对条件变量进行初始化,只能使用pthread_cond_init()函数。
Int pthread_cond_init(pthread_cond_t * cond, pthread_condattr_t * cond_attr);
Cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向pthread_condattr_t的指
针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样,我们可以用它来设置条件是进程内可用还是进程间可用,缺省值是PTHREAD_PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。
函数pthread_cond_wait()使线程阻塞在一个条件变量上,解锁时可以使用pthread_cond_signal()(只唤醒守侯着这个条件变量的一个线程)或使用pthread_cond_broadcast()函数(唤醒守侯着这个条件变量的所有线程)。如上例所示:
iny rc=pthread_cond_signal(&count_nonzero);
或者使用广播函数:
iny rc=pthread_cond_broadcast(&count_nonzero);
rc在成功时返回0,失败时返回一个非0值,反映发生错误的类型(EINVAL说明函数参数不是条件变量,ENOMEM说明系统没有可用的内存)。
注意:发送信号成功不表明一定有线程被唤醒,可能这时候没有线程在守侯该条件变量,并
且这个信号会丢失,不会被使用。如果有新的线程开始守侯该条件变量,那么必须要有新的信号才能唤醒它。
线程可以通过两个函数pthread_cond_wait()和pthread_cond_timedwait()来守侯条件变量,这个两个函数以一个条件变量和一个互斥量(应该在调用之前锁定)为参数,解除对互斥量的锁定,挂起线程的执行,并处于等待状态,直到条件变量接收到信号。如果该线程被条件变量唤醒,守侯函数再次自动锁定互斥量,并开始执行。
这两个函数的唯一区别是:pthread_cond_timedwait()允许用户给定一个时间间隔,过了这个间隔,函数总是返回,返回值为ETIMEDOUT,表示在时间间隔之内条件变量没接收到信号,阻塞也被解除。而如果没有信号pthread_cond_wait(),将无限期等待下去。
3、信号量
如果我们编写的程序中使用了多线程,那么在多用户多进程系统中,需要保证只有一个线程能够对某写资源进行排他性访问(这些资源叫做临界资源)。为防止多个程序访问一个资源引发的问题,我们需要有一种方法生成并使用一个记号,使得任意时刻只有一个线程拥有对该项资源的访问权。
信号量是一种特殊的变量,它只能取正整数值,对这些正整数只能采取两种操作:P操作(代表等待,关操作)和V操作(代表信号,开操作)。P/V操作的定义如下(假设我们有一个信号量sem):
P(sem):如果sem的值大于0,则sem减1;如果sem的值为0,则挂起该线程。
V(sem):如果有其它进程因等待sem而被挂起,则让它恢复执行;如果没有线程等待sem而被挂起,则sem加上1。
信号量的创建与打开
要使用信号量,首先必须创建一个信号量。创建信号量的函数如下:
# include <sya/types.h>
# include <sya/ipc.h>
# include <sya/sem.h>
int semget(key_t key,int nsems,int flag);
函数semget()用于创建一个新的信号量集,或打开一个已存在的信号量集。其中,参数key表示所创建或打开的信号量集的键。参数nsems表示创建的信号量集中信号量的个数,此参数只在创建一个新的信号量集时有效。参数flag表示调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过逻辑或表示,它低端的九个位是该信号量的权限,可以与键值IPC_CREATE做按位或操作以创建一个新的信号量,即使在设置了IPC_CREATE后给出的是一个现有信号量的键字,也并不是一个错误。如果IPC_CREATE标识在函数里用不着,函数就会忽略它的作用。我们可以使用IPC_CREATEIPC_EXCL标识来创建一个独一无二的新的信号量,如果该信号量已经存在,则返回一个错误。调用函数semget()的作用由参数keyflag决定,此函数调用成功时,返回值为信号量的引用标识符;调用失败时,返回值为-1
对信号量的操作
对信号量的操作使用如下函数:
# include <sya/types.h>
# include <sya/ipc.h>
# include <sya/sem.h>
int semop (int semid, struct sembuf semoparray[ ], size_t nops ) ;
参数semid是信号量集的引用id,semoparray[ ]是一个sembuf类型数组,sembuf结构用于指定调用semop()函数所做的操作,数组semoparray[ ]中元素的个数由nops决定。Sembuf的结构如下: