📄 1037.html
字号:
<br>
<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
shmat()<br>
<br>
系统调用: shmat();<br>
原型:int shmat ( int shmid, char *shmaddr, int shmflg);<br>
返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回- 1:errno = EINVAL (无效的IPC ID 值或者无效的地址)<br>
ENOMEM (没有足够的内存)<br>
EACCES (存取权限不够)<br>
如果参数a d d r的值为0,那么系统内核则试图找出一个没有映射的内存区域。我们推荐使用这种方法。你可以指定一个地址,但这通常是为了加快对硬件设备的存取,或者解决和其他程序的冲突。<br>
下面的程序中的调用参数是一个内存段的I P C标识符,返回内存段连接的地址:<br>
<br>
char *attach_segment(int shmid)<br>
{<br>
return(shmat(shmid, 0, 0));<br>
}<br>
<br>
一旦内存段正确地连接到进程以后,进程中就有了一个指向该内存段的指针。这样,以后就可以使用指针来读取此内存段了。但一定要注意不能丢失该指针的初值。<br>
<br>
<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
shmctl()<br>
<br>
系统调用:shmctl ( ) ;<br>
原型:int shmctl( int shmqid, int cmd, struct shmid_ds *buf );<br>
返回值: 0 ,如果成功。<br>
-1,如果失败:errno = EACCES (没有读的权限,同时命令是IPC_STAT)<br>
EFAULT(buf指向的地址无效,同时命令是IPC_SET和IPC_STAT )<br>
EIDRM (内存段被移走)<br>
EINVAL (shmqid 无效)<br>
EPERM (使用IPC_SET 或者IPC_RMID 命令,但调用进程没有写的权限)<br>
IPC_STAT 读取一个内存段的数据结构shmid_ds,并将它存储在buf参数指向的地址中。<br>
IPC_SET 设置内存段的数据结构shmid_ds中的元素ipc_perm的值。从参数buf中得到要设置的值。<br>
IPC_RMID 标志内存段为移走。<br>
命令IPC_RMID并不真正从系统内核中移走共享的内存段,而是把内存段标记为可移除。进程调用系统调用shmdt()脱离一个共享的内存段。<br>
<br>
<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
shmdt()<br>
<br>
系统调用:shmdt();<br>
调用原型:int shmdt ( char *shmaddr );<br>
返回值:如果失败,则返回- 1:errno = EINVAL (无效的连接地址)<br>
当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。但这不等于将共享内存段从系统内核中移走。当进程脱离成功后,数据结构shmid_ds中元素shm_nattch将减1。当此数值减为0以后,系统内核将物理上把内存段从系统内核中移走。<br>
<br>
<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
线程<br>
<br>
线程通常叫做轻型的进程。虽然这个叫法有些简单化,但这有利于了解线程的概念。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。进程往往需要它们自己的资源,但线程之间可以共享资源,所以线程更加节省内存。Mach的线程使得程序员可以编写并发运行的程序,而这些程序既可以运行在单处理器的机器上,也可以运行在多处理器的机器中。另外,在单处理器环境中,当应用程序执行容易引起阻塞和延迟的操作时,线程可以提高效率。<br>
用子函数pthread_create创建一个新的线程。它有四个参数:一个用来保存线程的线程变量、一个线程属性、当线程执行时要调用的函数和一个此函数的参数。例如:<br>
pthread_ta_thread ;<br>
pthread_attr_ta_thread_attribute ;<br>
void thread_function(void *argument);<br>
char * some_argument;<br>
pthread_create( &a_thread, a_thread_attribute, (void *)&thread_function,<br>
(void *) &some_argument);<br>
线程属性只指明了需要使用的最小的堆栈大小。在以后的程序中,线程的属性可以指定其他的值,但现在大部分的程序可以使用缺省值。不像UNIX系统中使用fork系统调用创建的进程,它们和它们的父进程使用同一个执行点,线程使用在pthread_create中的参数指明要开始执行的函数。<br>
<br>
现在我们可以编制第一个程序了。我们编制一个多线程的应用程序,在标准输出中打印“Hello Wo r l d”。首先我们需要两个线程变量,一个新线程开始执行时可以调用的函数。我们还需要指明每一个线程应该打印的信息。一个做法是把要打印的字符串分开,给每一个线程一个字符串作为开始的参数。请看下面的代码:<br>
void print_message_function( void *ptr );<br>
main( )<br>
{<br>
pthread_t thread1, thread2;<br>
char *message1 = "Hello";<br>
char *message2 = "Wo r l d " ;<br>
pthread_create( &thread1, pthread_attr_default,<br>
(void*)&print_message_function, (void*) message1);<br>
pthread_create(&thread2, pthread_attr_default,<br>
(void*)&print_message_function, (void*) message2);<br>
exit( 0 ) ;<br>
}<br>
void print_message_function( void *ptr )<br>
{<br>
char *message;<br>
message = (char *) ptr;<br>
printf("%s ", message);<br>
}<br>
<br>
程序通过调用pthread_create创建第一个线程,并将“Hello”作为它的启动参数。第二个线程的参数是“World”。当第一个线程开始执行时,它使用参数“Hello”执行函数print_message_function。它在标准输出中打印“Hello”,然后结束对函数的调用。线程当离开它的初始化函数时就将终止,所以第一个线程在打印完“Hello”后终止。当第二个线程执行时,它打印“World”然后终止。但这个程序有两个主要的缺陷。<br>
首先也是最重要的是线程是同时执行的。这样就无法保证第一个线程先执行打印语句。所以你很可能在屏幕上看到“World Hello”,而不是“Hello World”。请注意对exit的调用是父线程在主程序中使用的。这样,如果父线程在两个子线程调用打印语句之前调用exit,那么将不会有任何的打印输出。这是因为exit函数将会退出进程,同时释放任务,所以结束了所有的线程。任何线程(不论是父线程或者子线程)调用exit 都会终止所有其他线程。如果希望线程分别终止,可以使用pthread_exit函数。<br>
我们可以使用一个办法弥补此缺陷。我们可以在父线程中插入一个延迟程序,给子线程足够的时间完成打印的调用。同样,在调用第二个之前也插入一个延迟程序保证第一个线程在第二个线程执行之前完成任务。<br>
<br>
void print_message_function( void *ptr );<br>
main ( )<br>
{<br>
pthread_t thread1, thread2;<br>
char *message1 = "Hello”;<br>
char *message2 = "Wo r l d " ;<br>
pthread_create( &thread1, pthread_attr_default,<br>
(void *) &print_message_function, (void *) message1);<br>
sleep (10) ;<br>
pthread_create(&thread2, pthread_attr_default,<br>
(void *) &print_message_function, (void *) message2);<br>
sleep ( 10 ) ;<br>
exit (0) ;<br>
}<br>
void print_message_function( void *ptr )<br>
{<br>
char *message;<br>
message = (char *) ptr;<br>
printf("%s", message);<br>
pthread_exit(0) ;<br>
}<br>
<br>
这样是否达到了我们的要求了呢?不尽如此,因为依靠时间的延迟执行同步是不可靠的。这里遇到的情形和一个分布程序和共享资源的情形一样。共享的资源是标准的输出设备,分布计算的程序是三个线程。<br>
其实这里还有另外一个错误。函数sleep和函数e x i t一样和进程有关。当线程调用sleep时,整个的进程都处于睡眠状态,也就是说,所有的三个线程都进入睡眠状态。这样我们实际上没有解决任何的问题。希望使一个线程睡眠的函数是pthread_delay_np。例如让一个线程睡眠2秒钟,用如下程序:<br>
<br>
struct timespec delay;<br>
delay.tv_sec = 2;<br>
delay.tv_nsec = 0;<br>
pthread_delay_np( &delay );<br>
}<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
线程同步<br>
<br>
POSIX提供两种线程同步的方法,mutex和条件变量。mutex是一种简单的加锁的方法来控制对共享资源的存取。我们可以创建一个读/写程序,它们共用一个共享缓冲区,使用mutex来控制对缓冲区的存取。<br>
void reader_function(void);<br>
void writer_function(void);<br>
char buf f e r ;<br>
int buffer_has_item = 0;<br>
pthread_mutex_t mutex;<br>
struct timespec delay;<br>
main( )<br>
{<br>
pthread_t reader;<br>
delay.tv_sec = 2;<br>
delay.tv_nsec = 0;<br>
pthread_mutex_init(&mutex, pthread_mutexattr_default);<br>
pthread_create( &reader, pthread_attr_default, (void*)&reader_function,<br>
N U L L ) ;<br>
writer_function( )<br>
void writer_function(void)<br>
{<br>
while( 1 )<br>
{<br>
pthread_mutex_lock( &mutex );<br>
if ( buffer_has_item == 0 )<br>
{<br>
buffer = make_new_item();<br>
buffer_has_item = 1;<br>
}<br>
pthread_mutex_unlock( &mutex );<br>
pthread_delay_np( &delay );<br>
}<br>
}<br>
void reader_function(void)<br>
{<br>
while( 1 )<br>
{<br>
pthread_mutex_lock( &mutex );<br>
if ( buffer_has_item == 1)<br>
{<br>
consume_item( buffer );<br>
buffer_has_item = 0;<br>
}<br>
pthread_mutex_unlock( &mutex );<br>
pthread_delay_np( &delay );<br>
}<br>
}<br>
在上面的程序中,我们假定缓冲区只能保存一条信息,这样缓冲区只有两个状态,有一条信息或者没有信息。使用延迟是为了避免一个线程永远占有mutex。<br>
但mutex的缺点在于它只有两个状态,锁定和非锁定。POSIX的条件变量通过允许线程阻塞和等待另一个线程的信号方法,从而弥补了mutex的不足。当接受到一个信号时,阻塞线程将会被唤起,并试图获得相关的mutex的锁。<br>
<br>
<br>
<br>
[目录]<br>
<br>
--------------------------------------------------------------------------------<br>
<br>
<br>
使用信号量协调程序<br>
<br>
我们可以使用信号量重新看一下上面的读/写程序。涉及信号量的操作是semaphore_up、semaphore_down、semaphore_init、semaphore_destroy和semaphore_decrement。所有这些操作都只有一个参数,一个指向信号量目标的指针。<br>
void reader_function(void);<br>
void writer_function(void);<br>
char buffer ;<br>
Semaphore writers_turn;<br>
Semaphore readers_turn;<br>
main( )<br>
{<br>
pthread_t reader;<br>
semaphore_init( &readers_turn );<br>
semaphore_init( &writers_turn );<br>
/* writer must go first */<br>
semaphore_down( &readers_turn );<br>
pthread_create( &reader, pthread_attr_default,<br>
(void *)&reader_function, NULL);<br>
w r i t e r _ f u n c t i o n ( ) ;<br>
}<br>
void writer_function(void)<br>
{<br>
w h i l e ( 1 )<br>
{<br>
semaphore_down( &writers_turn );<br>
b u ffer = make_new_item();<br>
semaphore_up( &readers_turn );<br>
}<br>
}<br>
void reader_function(void)<br>
{<br>
w h i l e ( 1 )<br>
{<br>
semaphore_down( &readers_turn );<br>
consume_item( buffer );<br>
semaphore_up( &writers_turn );<br>
}<br>
}<br>
这个例子也没有完全地利用一般信号量的所有函数。我们可以使用信号量重新编写“Hello world” 的程序:<br>
void print_message_function( void *ptr );<br>
Semaphore child_counter;<br>
Semaphore worlds_turn;<br>
main( )<br>
{<br>
pthread_t thread1, thread2;<br>
char *message1 = "Hello";<br>
char *message2 = "Wo r l d " ;<br>
semaphore_init( &child_counter );<br>
semaphore_init( &worlds_turn );<br>
semaphore_down( &worlds_turn ); /* world goes second */<br>
semaphore_decrement( &child_counter ); /* value now 0 */<br>
semaphore_decrement( &child_counter ); /* value now -1 */<br>
/*<br>
* child_counter now must be up-ed 2 times for a thread blocked on it<br>
* to be released<br>
*<br>
* /<br>
pthread_create( &thread1, pthread_attr_default,<br>
(void *) &print_message_function, (void *) message1);<br>
semaphore_down( &worlds_turn );<br>
pthread_create(&thread2, pthread_attr_default,<br>
(void *) &print_message_function, (void *) message2);<br>
semaphore_down( &child_counter );<br>
/* not really necessary to destroy since we are exiting anyway */<br>
semaphore_destroy ( &child_counter );<br>
semaphore_destroy ( &worlds_turn );<br>
e x i t ( 0 ) ;<br>
}<br>
void print_message_function( void *ptr )<br>
{<br>
char *message;<br>
message = (char *) ptr;<br>
printf("%s ", message);<br>
fflush(stdout);<br>
semaphore_up( &worlds_turn );<br>
semaphore_up( &child_counter );<br>
p t h r e a d _ e x i t ( 0 ) ;<br>
}<br>
信号量c h i l d _ c o u n t e r用来强迫父线程阻塞,直到两个子线程执行完p r i n t f语句和其后的semaphore_up( &child_counter )语句才继续执行。<br>
Semaphore.h<br>
<br>
#ifndef SEMAPHORES<br>
#define SEMAPHORES<br>
#include<br>
#include<br>
typedef struct Semaphore<br>
{<br>
int v;<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -