POSIX:
POSIX 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX 是为了读音更像 UNIX)。电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。由ANSI和ISO标准化。
POSIX的诞生和Unix的发展是密不可分的,
Unix于70年代诞生于贝尔实验室,并于80年代向美各大高校分发V7版的源码以做研究。加利福尼亚大学伯克利分校在V7的基础上开发了BSD Unix。后来很多商业厂家意识到Unix的价值也纷纷以贝尔实验室的System V或BSD为基础来开发自己的Unix。
但由于各厂家对Unix的开发各自为政,造成Unix的版本相当混乱,给软件的可移植性带来很大的困难,对Unix的发展极为不利,为结束这种局面,IEEE开发了POSIX,POSIX在源代码级别上定义了一组最小的Unix(类Unix)操作系统接口,然而,POSIX并不局限于UNIX,许多其他的操作系统eg:Microsoft windows NT Linux等都支持(部分)POSIX标准。
POSIX 已然发展成为一个非常庞大的标准族,某些部分正处在开发过程中。
//---------------------------------
Pthread线程库:
POSIX 1003.1c定义了处理线程的一系列C语言类型的API函数,提供了一个可移植的多线程库,
称为Pthreads.
Pthreads现在已成为Linux操作系统中多线程接口的标准,并且广泛使用在大多数的unix平台上。针对windows操作系统,Pthreads也存在一个开放源代码的版本,称为pthreads-win32。
可以通过_POSIX_THREADS宏测试系统是否支持Pthread。 #ifdef _POSIX_THREADS
Pthreads库的函数原型定义在<pthread.h>中,在编写多线程程序时需要include该头文件
编译时需要连接libpthread库。
Pthread线程库提供的函数一般都已pthread 开头,eg:
前缀 功能集合
pthread_ 线程或子线程
pthread_attr_ 线程对象属性
pthread_mutex_ 互斥量
pthread_mutexattr 互斥量对象属性
pthread_cond_ 条件变量
pthread_condattr 条件变量属性
数据类型:
pthread_t:线程句柄
pthread_attr_t:线程属性
操作函数:
pthread_create():创建一个线程
pthread_exit():终止当前线程
pthread_cancel():中断另外一个线程的运行
pthread_join():阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init():初始化线程的属性
pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate():获取脱离状态的属性
pthread_attr_destroy():删除线程的属性
pthread_kill():向线程发送一个信号
同步函数:
用于 mutex 和条件变量
pthread_mutex_init() 初始化互斥锁
pthread_mutex_destroy() 删除互斥锁
pthread_mutex_lock():占有互斥锁(阻塞操作)
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
pthread_mutex_unlock(): 释放互斥锁
pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait(): 等待条件变量的特殊条件发生
Thread-local storage(或者以Pthreads术语,称作线程特有数据):
pthread_key_create(): 分配用于标识进程中线程特定数据的键
pthread_setspecific(): 为指定线程特定数据键设置线程特定绑定
pthread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete(): 销毁现有线程特定数据键
工具函数:
pthread_equal(): 对两个线程的线程标识号进行比较
pthread_detach(): 分离线程
pthread_self(): 查询线程自身线程标识号\
//---------------------------------
#include <pthread.h>
pthread_t tid[NUMT];
for(i=0; i< NUMT; i++) {
error = pthread_create(&tid[i],
NULL, /* default attributes please */
pull_one_url,
for(i=0; i< NUMT; i++) {
error = pthread_join(tid[i], NULL);
(void *)urls[i]);
//---------------------------------
线程创建
函数原型:int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);
返回值:若是成功建立线程返回0,否则返回错误的编号。
形式参数:pthread_t*restrict tidp要创建的线程的线程id指针;const pthread_attr_t *restrict attr创建线程时的线程属性;void* (start_rtn)(void)返回值是void类型的指针函数;void *restrict arg start_rtn的形参。
线程挂起:该函数的作用使得当前线程挂起,等待另一个线程返回才继续执行。也就是说当程序运行到这个地方时,程序会先停止,然后等线程id为thread的这个线程返回,然后程序才会断续执行。
函数原型:intpthread_join(pthread_tthread, void **value_ptr);
参数说明如下:thread等待退出线程的线程号;value_ptr退出线程的返回值。
返回值:若成功,则返回0;若失败,则返回错误号。
线程退出
函数原型:voidpthread_exit(void *rval_ptr);
获取当前线程id
函数原型:pthread_tpthread_self(void);
互斥锁
创建pthread_mutex_init;销毁pthread_mutex_destroy;加锁pthread_mutex_lock;解锁pthread_mutex_unlock。
条件锁
创建pthread_cond_init;销毁pthread_cond_destroy;触发pthread_cond_signal;广播pthread_cond_broadcast;等待pthread_cond_wait。
//---------------------------------
线程安全: 指代码能够被多个线程调用而不会产生错误的结果,大部分现行函数
可以利用Pthreads提供的工具------互斥量,条件变量和线程私有数据,实现线程的安全。
同步可以通过很多种方式实现,其中最常见的是互斥量,条件变量,信号量和事件,还可以通过消息传递机制,如UNIX管道,SOCKET ,POSIX消息队列,或者是其他异步进程间(本地或者网络)的通信协议。任何通信协议里都包含某种相同的同步机制,因为如果没有同步,则传送数据将导致混乱而不是通信。
线程,互斥量和条件变量时本书的主要话题,线程是计算机中的可执行单元,互斥量阻止线程间发生不可预期的冲突,一旦避免了冲突,条件变量让线程等待直到可以安全地执行,互斥量和条件变量都是用来同步线程间操作的。
线程就是进程里足以执行代码的部分,在大多数计算机系统中,这意味着线程应包括以下内容:当前指令位置指针(通常称为计数器或PC),栈顶指针(SP),通用寄存器,浮点或地址寄存器,线程可能还包括像处理器状态和协处理器寄存器等数据,线程不包括进程中的其他数据,如地址空间和文件描述符,一个进程中的所有线程共享文件和内存空间,包括程序文本段和数据段。
//---------------------------------------------
程序中使用线程标示符ID来表示线程,线程ID属于封装的pthread_t类型
Pthread线程通过调用你提供的某些函数开始,这个”线程函数”应该只有一个void*类型参数
并返回一个void*类型参数。通过向pthread_create()函数传送线程函数地址和线程函数调用的参数来创建线程。
当创建线程时,pthread_create函数返回一个pthread_t类型的线程ID,并保存在thread参数中,通过这个线程ID,程序可以引用该线程,线程可以通过调用pthread_self来获得一个线程ID,要对线程进行任何操作都必须通过线程ID。
Pthread_equal()函数来比较两个线程ID,只能比较二者是否相同。相等则Pthread_equal()函数返回非零值,否则返回零值。
分离线程: 意味着通知系统不再需要此线程,允许系统将分配给它的资源回收。
Pthread_detach()函数类分离它,线程可以分离自己,任何获知其ID的其他线程也可以随时分离它。
所以为了确保终止线程的资源还可以被进程使用,应该在每个线程结束时分离它们,一个没有被分离的线程终端会保留其虚拟内存,包括它们的堆栈和其他系统资源。
如果需要获得线程的返回值,或者需要获得线程的返回值,或者需要获知其何时结束,应该调用pthread_join函数,
//---------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>
#include "errors.h"
//thread start routine
void* thread_routine(void *arg)
{
return arg;
}
int main( int argc ,char *argv[])
{
pthread_t thread_id;
void *thread_result;
int status;
status = pthread_create(&thread_id, NULL,thread_routine,NULL);
if (status != 0)
{
printf("%d,%s",status,"create thread");
}
status = pthread_join(thread_id,&thread_result);
if (status != 0)
{
printf("%d,%s",status,"join thread");
}
if (thread_result == NULL)
{
return 0;
}
else
return 1;
}
创建线程:
进程的初始线程随着进程的创建而创建,main()函数。在一个完全支持线程编程的系统中,可能没有代码能够脱离线程运行。
其他线程通过显示的函数调用建立,Pthread系统中建立线程的主要方式是调用pthread_create()。
线程启动:
一旦线程被创建,最终它将开始执行机器指令,初始指令序列将导致在pthread_create()调用中指定的线程启动函数的执行。线程启动函数的运行参数也是在创建线程是指定,新线程使用参数NULL开始运行thread_routine函数中的用户代码。
在初始线程中,线程的启动函数(即main函数)是从程序外部被调用的,例如,很多UNIX系统中将程序链接到一个crt0.o文件上,该文件负责初始化进程,然后调用main函数,这是一个较小的实现区别,还有其他的区别,如,main函数的调用参数(argc和argv)与普通线程的启动函数不同(void*参数),另外,如果普通线程从启动函数中返回,则线程终止而其他线程依然可以运行,但初始进程从main函数中返回时,进程终止(进程内所有线程也被终止)。如果你希望在初始线程终止时,进程中的其他线程继续执行,则需要在初始线程中调用pthread_exit()而不是从main函数中返回。
另一个重要的区别是:在大多数系统中,初始线程运行在默认进程堆栈上,该堆栈可以增长到足够的尺寸,而在某些实现中,普通线程的堆栈空间是受限的。
运行和阻塞:
Pthread_join()
终止:
线程通常从启动函数中返回来终止自己
Pthread_exit()退出线程
Pthread_cancel()取消线程
线程在调用完每个清理过程后将进入终止态,如果该线程没有被分离,它所有的资源就没有被回收,它还可以被其他线程调用pthread_join连接,这时进程被称为“僵”线程,因为即使已经死了 但还存在,僵线程可能会保留其运行时的大部分甚至所有资源,因此不应该让线程长时间处于这种状态。如果处于终止态,且线程已经被分离,则它立刻进入下一节----------回收。
Pthread_cleanup_push()
Pthread_cleanup_pop()
回收:
如果使用detachstate属性(设为PTHREAD_CREATE_DETACH)建立线程,或者调用pthread_detach分离线程,则当线程结束时将被立刻回收。
如果终止线程没有被分离,则它将一直处于终止态直到被分离(通过pthread_detach)或者被连接(通过pthread_join).。线程一旦被分离,就不能再访问它了。
回收将释放所有在线程终止时未释放的系统和进程资源,包括存线程返回值的内存空间,堆栈,保存寄存器状态的内存空间等。其中一些资源可能已在线程终止时被释放,但必须记住:在线程终止后上述资源就不该被访问了,一旦线程被回收,线程ID就无效了,不能再连接它,取消它或者执行其他任何操作。
//--------------------------------------------
同步
使线程同步最通用和常用的方法就是确保对相同(或相关)数据的内存访问“互斥地”进行,即一次只能允许一个线程写数据,其他线程必须等待。Pthread使用了一种特殊形式的信号灯--------互斥量
经验表明,正确使用互斥量比使用像通用信号灯之类的其他同步模型要容易,还能很容易地使用互斥量与条件变量结合建立任何同步模型,互斥量简单,灵活,并能被有效实现。
Pthread_mutes_trylock() 函数试图加锁互斥量。
创建和销毁互斥量
Pthread_mutes_t mutex = PTHREAD_MUTEX_INITIALIZER;
Int pthread_nutex_init( pthread_mutex_t* mutex, pthread_mutexattr_t *attr);
Int pthread_mutex_destroy( pthread_mutex_t*mutex);
程序中的互斥量是用 pthread_mutex_t 类型的变量来表示的,不能拷贝互斥量变量可以拷贝互斥量指针,这样就可以使多个函数或线程共享互斥量来实现同步。
使用宏 PTHREAD_MUTEX_INITIALIZER 来初始化声明具有默认属性的静态互斥量。
#include <pthread.h>
typedef struct my_struct_tag{
pthread_mutex_t mutex;
int value;
}my_struct_t;
my_struct_t data = {PTHREAD_MUTEX_INITIALIZER,0};
int main(int argc,char*argv[])
{
return 0;
}
通常不能静态地初始化一个互斥量,例如当使用malloc动态分配一个包含互斥量的数据结构时,这时,应该使用pthread_mutex_init()调用来动态地初始化互斥量,也可以动态地初始化静态声明的互斥量,但必须保证每个互斥量在使用前被初始化,而且只被初始化一次。
也可以在创建任何线程之前初始化它,如通过调用pthread_once()。如果需要初始化一个非缺省属性的互斥量,必须使用动态初始化。
如下程序:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>
typedef struct my_struct_tag{
pthread_mutex_t mutex;
int value;
}my_struct_t;
int main(int argc,char*argv[])
{
my_struct_t * data;
int status;
data = malloc(sizeof(my_struct_t));
if (data == NULL)
printf("%s","Allocate structure");
status = pthread_mutex_init(&data->mutex,NULL);
if (status != 0)
printf("%d,%s",status,"init mutex");
status = pthread_mutex_destroy(&data->mutex);
if(status != 0)
printf("%d,%s",status,"Destroy mutex");
(void)free(data);
return 0;
}
将互斥量与它要保护的数据明显地联系起来是个不错的主意,如果可以的话,将互斥量和数据定义在一起,
通过pthread_mutex_init调用动态初始化的互斥量时,应该调用pthread_mutex_destroy()来释放它,不需要释放一个使用PTHREAD_MUTEX_INITIALIZER宏静态初始化的互斥量。
加锁和解锁互斥量
Int pthread_mutex_lock( pthread_mutex_t * mutex);
Int pthread_mutex_trylock( pthread_mutex_t * mutex);
Int pthread_mutex_unlock( pthread_mutex_t * mutex);
通过调用 pthread_mutex_lock或pthread_mutex_trylock锁住互斥量,处理共享数据,然后调用pthread_mutex_unlock解锁互斥量,为确保线程能够读取一组变量的一致的值,需要在任何读写这些变量的代码段周围锁住互斥量。
当调用线程已经锁住互斥量之后,就不能再加锁该互斥量,也不能解锁一个已经解锁的互斥量,也不能解锁有其他线程锁住的互斥量。
创建和释放条件变量:
Pthread_cond_t cod = PTHREAD_COND_INITIALIZER;
Int pthread_cond_init(pthread_cond_t * cond, pthread_condattr_t *condattr);
Int pthread_cond_destroy(pthread_cond_t * cond);
程序中由pthread_cond_t类型的变量来表示条件变量,永远不要拷贝条件变量。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>
typedef struct my_struct_tag{
pthread_mutex_t mutex;
pthread_cond_t cond;
int value;
}my_struct_t;
my_struct_t data = {PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER
,0}
int main(int argc,char*argv[])
{
return 0;
}
有时无法静态地初始化一个条件变量,例如,当使用malloc分配一个包含条件变量的结构时,这时,你需要调用pthread_cond_init()来动态初始化条件变量,还可以动态初始化静态声明的条件变量,但是必须确保每个条件变量在使用之前初始化且仅初始化一次,你可以在建立任何线程前初始化它,或者使用pthread_once()
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>
typedef struct my_struct_tag{
pthread_mutex_t mutex;
pthread_cond_t cond;
int value;
}my_struct_t;
my_struct_t data = {PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER
,0}
int main(int argc,char*argv[])
{
my_struct_t * data;
int status;
data = malloc(sizeof(my_struct_t));
if(data == NULL)
printf("%s","Allocate structure");
status = pthread_mutex_init(&data->mutex,NULL);
if(status != 0)
printf("%s","init mutex");
status = pthread_cond_init(&data->cond,NULL);
if (status != 0)
printf("%s","init condition");
status = pthread_cond_destroy(&data->cond);
if(status != 0)
printf("%s","destroy condition");
status = pthread_mutex_destroy(&data->mutex);
if(status != 0)
printf("%s","destroy mutex");
(void)free(data);
return 0;
}
当动态初始化条件变量时,应该在不需要它时调用pthread_cond_destroy()来释放它,不必释放一个通过PTHREAD_COND_INITIALIZER宏静态初始化的条件变量。