线程 [ 4.4.3- end]




4.4.3  Mutex Deadlocks
Mutexes 提供了一种允许一个线程让另一个线程阻塞的机制。但是这也为产生一种新的错误带来了可能---死锁。

当一个或则多个线程等待不可能发生的事件时就会产生死锁。
当同一个线程对同一个mutex加锁两次时就可能发生死锁。这也跟锁的类型有关。有三种锁,
    1> 当为fast mutex(默认的种类)类型的锁时,将会发生死锁。因为它将会被阻塞直到原来加的锁被解除,而这是不可能的。
    2> 当它为 recursive mutex(递归类型)的锁时,不会发生死锁。递归类型的锁可以被同一个线程安全的加锁多次。mutex会记录在它上面调用了多少次pthread_mutex_lock,该线程也
必须调用相同次数的 pthread_mutex_unlock。这样才可以真正的解锁,另外一个线程才可以锁上mutex.
    3> GNU/Linux将会标记和检测在 错误检测mutext(error-checking mutex)上的再次枷锁。在这种锁上连续的调用pthread_mutex_lock枷锁将会返回 失败码 EDEADLK。
 默认情况为第一种锁,要创建另外两种锁需要首先创建一个pthread_mutexattr_t变量,再调用pthread_mutexattr_init,然后调用pthread_mutexattr_setking_np设置mutex的类型。
第一个参数为一个mutex attribute类型的指针。第二个参数指定了mutex的类型,可以为,
    <1>PTHREAD_MUTEX_RECURSIVE_NP.递归类型(fast mutex)mutex。
    <2> PTHREAD_MUTEX_ERRORCHECK_NP,错误检测型。
    <3> 默认为 fast mutex类型.
销毁一个mutex属性对象使用 pthread_mutexattr_destroy
一般的顺序如下,
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
pthread_mutexattr_init (&attr);
pthread_mutexattr_setkind_np (&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
pthread_mutex_init (&mutex, &attr);
pthread_mutexattr_destroy (&attr);
4.4.5 线程的信号量 (Semaphores for Threads)
semaphore 是一种计数器,它可以用在多线程同步方面。GNU/Linux 确保对semaphore 的检测修改是原子的。每一个semphore 有一个计数值,是一个非负整数。一个semphore支持
两种操作:
1> wait 操作使得它的计数值减 1.如果计数值已经为0了,那么操作将被阻塞直到变为正值。
2> post 操作使得它的计数值增 1.如果semaphore的计数值已经是0,有其他线程阻塞在该线号量的wait操作上,那么这些阻塞的线程中将有一个变为非阻塞它的wait操作将完成(这也导致
这个信号量的计数值返回到0)。
请注意:GNU/Linux提供了两种有些不同的信号量的实现。一种是这里所介绍的 POSIX标准的 信号量的实现。另一种是用在进程间通信中,我们将在 5.2 中介绍。
  一个信号量由一个sem_t类型的变量代表。在使用前你需要使用 sem_init函数把它初始化。当你不再需要它的时候可以调用 sem_destroy 销毁它。
  调用 sem_wait 执行信号量的 wait 操作,调用 sem_post 执行信号量的 post 操作。一个非阻塞的wait函数为 sem_trywait,他和 pthread_mutex_trylock类似--它在一个信号量
为零时本应该阻塞时,却执行立即返回,返回的错误码是 EAGAIN。
  GNU/Linux 提供了函数 sem_getvalue 取得一个信号量的当前值。你不应该使用这个功能来判断去执行post还是 wait。因为这样做可能导致竞争(race condition):可能另外一个线程在你
执行sem_getvalue期间改变了它的值,又调用了另外一个信号量的函数。
  使用信号量我们来一个例子,
Listing 4.12 ( job-queue3.c) Job Queue Controlled by a Semaphore
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
struct job {
   /* Link field for linked list.   */
   struct job* next;
   /* Other fields describing work to be done... */
};
/* A linked list of pending jobs.    */
struct job* job_queue;
/* A mutex protecting job_queue. */
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
/* A semaphore counting the number of jobs in the queue.  */
sem_t job_queue_count;
/* Perform one-time initialization of the job queue.  */
void initialize_job_queue ()
{
  /* The queue is initially empty. */
  job_queue = NULL;
  /* Initialize the semaphore which counts jobs in the queue.  Its
     initial value should be zero. */
  sem_init (&job_queue_count, 0, 0);
}
/* Process queued jobs until the queue is empty.  */
void* thread_function (void* arg)
{
  while (1) {
    struct job* next_job;
    /* Wait on the job queue semaphore. If its value is positive,
       indicating that the queue is not empty, decrement the count by
       1. If the queue is empty, block until a new job is enqueued. */
    sem_wait (&job_queue_count);
    /* Lock the mutex on the job queue. */
    pthread_mutex_lock (&job_queue_mutex);
    /* Because of the semaphore, we know the queue is not empty. Get
       the next available job. */
    next_job = job_queue;
    /* Remove this job from the list. */
    job_queue = job_queue->next;
    /* Unlock the mutex on the job queue because we’re done with the
       queue for now. */
    pthread_mutex_unlock (&job_queue_mutex);
    /* Carry out the work. */
    process_job (next_job);
    /* Clean up. */
    free (next_job);
  }
  return NULL;
}
/* Add a new job to the front of the job queue.  */
void enqueue_job (/* Pass job-specific data here...
{
  struct job* new_job;
  /* Allocate a new job object. */
  new_job = (struct job*) malloc (sizeof (struct job));
  /* Set the other fields of the job struct here... */
  /* Lock the mutex on the job queue before accessing it.  */
  pthread_mutex_lock (&job_queue_mutex);
  /* Place the new job at the head of the queue. */
  new_job->next = job_queue;
  job_queue = new_job;
  /* Post to the semaphore to indicate that another job is available. If
     threads are blocked, waiting on the semaphore, one will become
     unblocked so it can process the job. */
  sem_post (&job_queue_count);
  /* Unlock the job queue mutex. */
  pthread_mutex_unlock (&job_queue_mutex);
}

4.4.6 条件变量
我们已经演示了怎样使用一个 mutex保护一个变量,使得这个变量免受同时访问。怎样使用 信号量 实现一个
共享计数器。而条件变量( condition variable )是 GNU/Linux提供的第三个同步工具。
   假如你写了一个线程函数,这个函数执行一个死循环,每一次迭代都作某项工作。这项工作只在某个标志为1时才执行,
其他情况执行下一次循环。在多线程环境下,它需要一个mutex 来保护。这样是正确的,但是也是低效的。一个线程将
浪费很多CPU时间当这个标志非置位时。因为线程一直在对这个标志不停的检查。而你真正想做的是当标志没有置位时,让
线程sleep直到该标志置位。
如下是个没有使用条件变量的例子,
Listing 4.13 (spin-condvar.c) A Simple Condition Variable Implementation
#include <pthread.h>
int thread_flag;
pthread_mutex_t thread_flag_mutex;
void initialize_flag ()
{
  pthread_mutex_init (&thread_flag_mutex, NULL);
  thread_flag = 0;
}
/* Calls do_work repeatedly while the thread flag is set; otherwise
    spins. */
void* thread_function (void* thread_arg)
{
  while (1) {
     int flag_is_set;
     /* Protect the flag with a mutex lock. */
     pthread_mutex_lock (&thread_flag_mutex);
     flag_is_set = thread_flag;
     pthread_mutex_unlock (&thread_flag_mutex);
     if (flag_is_set)
       do_work ();
     /* Else don’t do anything.   Just loop again. */
  }
  return NULL;
}
/* Sets the value of the thread flag to FLAG_VALUE.   */
void set_thread_flag (int flag_value)
{
  /* Protect the flag with a mutex lock. */
  pthread_mutex_lock (&thread_flag_mutex);
  thread_flag = flag_value;
  pthread_mutex_unlock (&thread_flag_mutex);
}

使用条件变量可以使你限定在什么条件下一个线程执行,在什么条件下一个线程阻塞。由于每个线程都可能改变这个条件,你应当
正确的使用它。Linux 保证在你设置的条件下线程阻塞,而当这个条件改变时线程阻塞解除。
    如同使用一个信号量,一个线程A可能在对一个条件变量执行 wait 操作时阻塞掉,而当另外一个线程B对该条件变量执行完signal
操作后,线程A可能解除阻塞。 跟使用一个信号量不同的地方是,条件变量没有计数器或则另外的记录内存空间。如果线程B在 线程A对
条件变量执行wait之前,对条件变量执行了signal操作,那么信号将丢失。而只有,线程A执行对条件变量的 wait后,线程B对条件
变量的signal操作对于线程A才是有效的。
    进行如下的修改,让上面的例子更高效,
    1> 在thread_funtion 中检查flag标志。如果falg没有置位,让线程等待一个条件变量。
    2> set_thread_flag 函数在改变了flag值之后,执行对条件变量的signal操作。这样,如果thread_funtion阻塞在了条件
    变量上,那么此时它将被解除阻塞。
这里有个问题:对条件变量的check,signal,wait操作间有冲突(race condition)。可以设想,thread_function 检查flag
标志,发现它没有置位。这时候,Linux调度器使 thread_function 暂停,恢复了 主线程。恰好,主线程执行了set_thread_flag
操作,它设置 flag,并对条件变量执行了signal操作。这样在 thread_function 对条件变量执行wait操作前发出的signal信号
会丢失。从而,thread_function 可能永远阻塞。
    为了解决这个问题,我们需要一种使用单独的mutex把 flag 和 条件变量锁在一起的方法。幸运的是,GNU/Linux提供了这种机制。
为避免以上提到的冲突,每一个条件变量必须和一个mutex一起使用。使用这种机制时,你应该遵循如下步骤:
    1> 在thread_function循环中 锁住mutex,读取 flag的值。
    2> 如果flag置位了,解除 mutex,并执行 work 函数。
    3> 如果flag没有置位,mutex自动解锁并且对条件变量执行wait操作。
重要的地方在第三步,在GNU/Linux中,你可以自动的解除mutex锁和等待条件变量,保证在这期间不会有其他线程介入。这就去除了上面
我们提到的signal信号丢失的可能。

pthread_cond_t 即为条件变量类型。记住条件变量必须和一个mutex一块儿使用。如下为操作条件变量的函数:
    *    pthread_cond_init ,初始化一个条件变量
    *    pthread_cond_signal,对条件变量执行signal操作。如果有线程阻塞在相关的条件变量上,那么该线程会被解除阻塞。如果没有
        线程阻塞在相关的条件变量上,该sigal被忽略。
        pthread_cond_broadcast,使得所有阻塞在相关的条件变量上的线程解除阻塞。
    *    pthread_cond_wait,将阻塞对条件变量执行wait操作的线程,直到条件变量被执行了signal操作。它的第二个参数为一个指向
        pthread_mutex_t的实例的指针。在调用 pthread_cond_wait 之前,mutex必须已经执行了上锁操作。
当你执行一个可能改变条件变量的操作时要遵循如下步骤:
    1. 对与条件变量相关的mutex上锁。
    2. 执行可能改变条件变量的操作。
    3. 执行对条件变量的signal或者broadcast操作。
    4. 对与条件变量相关的mutex解锁。
如下,
Listing 4.14 (condvar.c) Control a Thread Using a Condition Variable
#include <pthread.h>
int thread_flag;
pthread_cond_t thread_flag_cv;
pthread_mutex_t thread_flag_mutex;
void initialize_flag ()
{
  /* Initialize the mutex and condition variable.    */
  pthread_mutex_init (&thread_flag_mutex, NULL);
  pthread_cond_init (&thread_flag_cv, NULL0);
  /* Initialize the flag value. */
  thread_flag = 0;
}
/* Calls do_work repeatedly while the thread flag is set; blocks if
    the flag is clear. */
void* thread_function (void* thread_arg)
{
  /* Loop infinitely. */
  while (1) {
     /* Lock the mutex before accessing the flag value. */
     pthread_mutex_lock (&thread_flag_mutex);
     while (!thread_flag)
       /* The flag is clear. Wait for a signal on the condition
           variable, indicating that the flag value has changed. When the
           signal arrives and this thread unblocks, loop and check the
           flag again. */
       pthread_cond_wait (&thread_flag_cv, &thread_flag_mutex);
     /* When we’ve gotten here, we know the flag must be set. Unlock
        the mutex. */
     pthread_mutex_unlock (&thread_flag_mutex);
     /* Do some work. */
     do_work ();
  }
  return NULL;
}
/* Sets the value of the thread flag to FLAG_VALUE.    */
void set_thread_flag (int flag_value)
{
  /* Lock the mutex before accessing the flag value. */
  pthread_mutex_lock (&thread_flag_mutex);
  /* Set the flag value, and then signal in case thread_function is
      blocked, waiting for the flag to become set. However,
      thread_function can’t actually check the flag until the mutex is
      unlocked. */
  thread_flag = flag_value;
  pthread_cond_signal (&thread_flag_cv);
  /* Unlock the mutex. */
  pthread_mutex_unlock (&thread_flag_mutex);
}

4.5 GNU/Linux 线程实现
POSIX线程在GUN/Linux上的实现跟在其它类UNIX操作系统上不一样:在 GUN/Linux中,线程同进程类似。当你调用 pthread_create 去创建一个新的
线程时,Linux就会创建一个新的进程来运行这个线程。然而,这个进程和你用fork创建的进程不同,它和创建它的进程共享地址空间和资源。
Listing 4.15 (thread-pid) Print Process IDs for Threads
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void* thread_function (void* arg)
{
        fprintf (stderr, "child thread pid is %d /n", (int) getpid ());
        printf("/tReceived: %d/n", *((int*)arg));
        /* Spin forever. */
        while (1);
        return NULL;
}
int main ()
{
        pthread_t thread;
        int test_address_space = 555;
        fprintf (stderr, "main thread pid is %d /n", (int) getpid());
        pthread_create (&thread, NULL, &thread_function, (void*)&test_address_space);
        /* Spin forever*/
        while (1);
        return 0;
}

4.5.1 信号处理
   Because each thread is a separate process, and because a signal is delivered to a par-
ticular process, there is no ambiguity about which thread receives the signal.Typically,
signals sent from outside the program are sent to the process corresponding to the
main thread of the program. For instance, if a program forks and the child process
execs a multithreaded program, the parent process will hold the process id of the main
thread of the child process’s program and will use that process id to send signals to its
child.This is generally a good convention to follow yourself when sending signals to a
multithreaded program.

4.6 Processes Vs. Threads
 对于到底使用进程还是使用线程给出你如下的几点建议,
  # All threads in a program must run the same executable. A child process, on the
  other hand, may run a different executable by calling an exec function.
 
  # An errant thread can harm other threads in the same process because threads
  share the same virtual memory space and other resources. For instance, a wild
  memory write through an uninitialized pointer in one thread can corrupt
  memory visible to another thread.
  An errant process, on the other hand, cannot do so because each process has a
  copy of the program’s memory space.
 
 # Copying memory for a new process adds an additional performance overhead
  relative to creating a new thread. However, the copy is performed only when
  the memory is changed, so the penalty is minimal if the child process only reads
  memory.
 # Threads should be used for programs that need fine-grained parallelism. For
  example, if a problem can be broken into multiple, nearly identical tasks, threads
  may be a good choice. Processes should be used for programs that need coarser
  parallelism.
 # Sharing data among threads is trivial because threads share the same memory.
  (However, great care must be taken to avoid race conditions, as described previ-
  ously.) Sharing data among processes requires the use of IPC mechanisms, as
  described in Chapter 5.This can be more cumbersome but makes multiple
  processes less likely to suffer from concurrency bugs.





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值