📄 99.htm
字号:
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参 <br>
数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分 <br>
离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非 <br>
常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号 <br>
和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线 <br>
程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的 <br>
线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函 <br>
数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不 <br>
要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。 <br>
另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数 <br>
pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来, <br>
我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。 <br>
#include <pthread.h> <br>
#include <sched.h> <br>
pthread_attr_t attr; <br>
pthread_t tid; <br>
sched_param param; <br>
int newprio=20; <br>
<br>
pthread_attr_init(&attr); <br>
pthread_attr_getschedparam(&attr, ¶m); <br>
param.sched_priority=newprio; <br>
pthread_attr_setschedparam(&attr, ¶m); <br>
pthread_create(&tid, &attr, (void *)myfunction, myarg); <br>
<br>
4 线程的数据处理 <br>
和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的 <br>
数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须 <br>
当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个 <br>
函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题, <br>
函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一 <br>
个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修 <br>
改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编 <br>
译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须 <br>
使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线 <br>
程数据时的有关知识。 <br>
<br>
4.1 线程数据 <br>
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里, <br>
还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象, <br>
在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是 <br>
不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的 <br>
出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能 <br>
是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变 <br>
量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在 <br>
各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不 <br>
同的,在同一个线程里,它代表同样的数据内容。 <br>
和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键 <br>
读取线程数据;删除键。 <br>
创建键的函数原型为: <br>
extern int pthread_key_create __P ((pthread_key_t *__key, <br>
void (*__destr_function) (void *))); <br>
第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果 <br>
这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上 <br>
的内存块。这个函数常和函数 <br>
pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起 <br>
使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调 <br>
用pthread_once时它执行这个函数,以后的调用将被它忽略。 <br>
<br>
在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个 <br>
函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形 <br>
界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用 <br>
线程数据。 <br>
/* 声明一个键*/ <br>
pthread_key_t myWinKey; <br>
/* 函数 createWindow */ <br>
void createWindow ( void ) { <br>
Fl_Window * win; <br>
static pthread_once_t once= PTHREAD_ONCE_INIT; <br>
/* 调用函数createMyKey,创建键*/ <br>
pthread_once ( & once, createMyKey) ; <br>
/*win指向一个新建立的窗口*/ <br>
win=new Fl_Window( 0, 0, 100, 100, "MyWindow"); <br>
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/ <br>
setWindow(win); <br>
/* 将窗口指针值绑定在键myWinKey上*/ <br>
pthread_setpecific ( myWinKey, win); <br>
} <br>
<br>
/* 函数 createMyKey,创建一个键,并指定了destructor */ <br>
void createMyKey ( void ) { <br>
pthread_keycreate(&myWinKey, freeWinKey); <br>
} <br>
<br>
/* 函数 freeWinKey,释放空间*/ <br>
void freeWinKey ( Fl_Window * win){ <br>
delete win; <br>
} <br>
<br>
这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口 <br>
变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了 <br>
函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下: <br>
extern int <br>
pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer)); <br>
extern void *pthread_getspecific __P ((pthread_key_t __key)); <br>
这两个函数的参数意义和使用方法是显而易见的。要注意的是,用 <br>
pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以 <br>
回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将 <br>
被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据 <br>
所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor <br>
函数。线程数据的释放必须在释放键之前完成。 <br>
<br>
4.2 互斥锁 <br>
互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设 <br>
各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。 <br>
我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假 <br>
定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。 <br>
<br>
void reader_function ( void ); <br>
void writer_function ( void ); <br>
<br>
char buffer; <br>
int buffer_has_item=0; <br>
pthread_mutex_t mutex; <br>
struct timespec delay; <br>
void main ( void ){ <br>
pthread_t reader; <br>
/* 定义延迟时间*/ <br>
delay.tv_sec = 2; <br>
delay.tv_nec = 0; <br>
/* 用默认属性初始化一个互斥锁对象*/ <br>
pthread_mutex_init (&mutex,NULL); <br>
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); <br>
writer_function( ); <br>
} <br>
<br>
void writer_function (void){ <br>
while(1){ <br>
/* 锁定互斥锁*/ <br>
pthread_mutex_lock (&mutex); <br>
if (buffer_has_item==0){ <br>
buffer=make_new_item( ); <br>
buffer_has_item=1; <br>
} <br>
/* 打开互斥锁*/ <br>
pthread_mutex_unlock(&mutex); <br>
pthread_delay_np(&delay); <br>
} <br>
} <br>
<br>
<br>
void reader_function(void){ <br>
while(1){ <br>
pthread_mutex_lock(&mutex); <br>
if(buffer_has_item==1){ <br>
consume_item(buffer); <br>
buffer_has_item=0; <br>
} <br>
pthread_mutex_unlock(&mutex); <br>
pthread_delay_np(&delay); <br>
} <br>
} <br>
这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包 <br>
含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数 <br>
表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 <br>
pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数 <br>
pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有 <br>
两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中 <br>
的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属 <br>
性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有 <br>
PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE <br>
和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选 <br>
用最后一个默认属性。 <br>
pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用 <br>
pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个 <br>
线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻 <br>
塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了 <br>
pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。 <br>
上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可 <br>
能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁, <br>
例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁 <br>
2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数 <br>
pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息, <br>
程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但 <br>
最主要的还是要程序员自己在程序设计注意这一点。 <br>
<br>
4.3 条件变量 <br>
前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个 <br>
明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另 <br>
一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件 <br>
变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发 <br>
生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多 <br>
个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。 <br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -