线程与线程同步机制

线程

一、概念:

  线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务。
  线程,轻量级,不拥有自己独立的内存资源,共享进程的代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户ID和组ID等资源。
  线程拥有自己独立的栈,因此也有自己独立的局部变量。
  一个进程可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少要有一个主线程。

二、线程函数

  1.创建线程

    int pthread_create (pthread_t* restrict thread,
    const pthread_attr_t* restrict attr,
    void* (*start_routine) (void*),
    void* restrict arg);

    thread-     线程ID,输出参数。
            pthread_t即unsigned long int。

    attr -        线程属性,NULL表示缺省属性。
            pthread_attr_t可能是整型也可能是结构,
            因实现而异。

    start_routine-     线程过程函数指针,
            参数和返回值的类型都是void*。
            启动线程本质上就是调用一个函数,
            只不过是在一个独立的线程中调用的,
            函数返回即线程结束。

    arg -        传递给线程过程函数的参数。
            线程过程函数的调用者是系统内核,
            而非用户代码,
            因此需要在创建线程时指定参数。

  2.等待线程

    int pthread_join (pthread_t thread, void** retval);
    等待thread参数所标识的线程结束,成功返回0,失败返回错误码。

  3.获取线程自身的ID

    pthread_t  pthread_self (void);
    成功返回调用线程的ID,不会失败。

  4.比较两个线程的ID

    int pthread_equal (pthread_t t1, pthread_t t2);
    若参数t1和t2所标识的线程ID相等,则返回非零,否则返回0。

  5.终止线程

    ① 从线程过程函数中return。

    ② 调用pthread_exit函数。

    void pthread_exit (void* retval);

    retval -        和线程过程函数的返回值语义相同。

    注意:在任何线程中调用exit函数都将终止整个进程。

  6.线程执行轨迹

    ① 同步方式(非分离状态):
     创建线程之后调用pthread_join函数等待其终止,并释放线程资源。
    ② 异步方式(分离状态):
     无需创建者等待,线程终止后自行释放资源。

    int pthread_detach (pthread_t thread);
    使thread参数所标识的线程进入分离(DETACHED)状态。
    处于分离状态的线程终止后自动释放线程资源,且不能被pthread_join函数等待。
    成功返回0,失败返回错误码。

  7.取消线程

    ① 向指定线程发送取消请求
      int pthread_cancel (pthread_t thread);
      成功返回0,失败返回错误码。
    注意:该函数只是向线程发出取消请求,并不等待线程终止。
      缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。
      在取消点处,线程检查其自身是否已被取消了,并做出相应动作。
      当线程调用一些特定函数时,取消点会出现。

    ② 设置调用线程的可取消状态
      int pthread_setcancelstate (int state,int* oldstate);
      成功返回0,并通过oldstate参数输出原可取消状态(若非NULL),失败返回错误码。
      state取值:
      PTHREAD_CANCEL_ENABLE - 接受取消请求(缺省)。
      PTHREAD_CANCEL_DISABLE - 忽略取消请求。

    ③ 设置调用线程的可取消类型
      int pthread_setcanceltype (int type, int* oldtype);
      成功返回0,并通过oldtype参数输出原可取消类型,(若非NULL),失败返回错误码。
      type取值:
      PTHREAD_CANCEL_DEFERRED - 延迟取消(缺省)。
        被取消线程在接收到取消请求之后并不立即响应,
        而是一直等到执行了特定的函数(取消点)之后再响应该请求。
      PTHREAD_CANCEL_ASYNCHRONOUS - 异步取消。
        被取消线程可以在任意时间取消,不是非得遇到取消点才能被取消。
        但是操作系统并不能保证这一点。

 线程同步

  一、互斥锁(mutex)    

    通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。

1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
2 int pthread_mutex_lock(pthread_mutex_t* mutex); 
3 int pthread_mutex_destroy(pthread_mutex_t * mutex); 
4 int pthread_mutex_unlock(pthread_mutex_t * mutex);

    ① 互斥量被初始化为非锁定状态

    ② 线程1调用pthread_mutex_lock函数,立即返回,互斥量呈锁定状态;

    ③ 线程2调用pthread_mutex_lock函数,阻塞等待;

    ④ 线程1调用pthread_mutex_unlock函数,互斥量呈非锁定状态; 

    ⑤ 线程2被唤醒,从pthread_mutex_lock函数中返回,互斥量呈锁定状态

    特点:
      互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,

      直到这个线程unlock,其他的线程才开始可以利用这 个资源。

互斥锁类型:
① 普通锁 (PTHREAD_MUTEX_NORMAL)
      互斥锁默认类型。当一个线程对一个普通锁加锁以后,其余请求该锁的线程将形成一个等待队列,并在该锁解锁后按照优先级获得它,这种锁类型保证了资源分配的公平性。一个线程如果对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。
② 检错锁(PTHREAD_MUTEX_ERRORCHECK)
     一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK;对一个已经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁操作返回EPERM;
          ③ 嵌套锁(PTHREAD_MUTEX_RECURSIVE)
     该锁允许一个线程在释放锁之前多次对它加锁而不发生死锁;其他线程要获得这个锁,则当前锁的拥有者必须执行多次解锁操作;对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM
④ 默认锁(PTHREAD_MUTEX_ DEFAULT)
     一个线程如果对一个已经加锁的默认锁再次加锁,或者虽一个已经被其他线程加锁的默认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果;这种锁实现的时候可能被映射成上述三种锁之一;

  二、信号量(sem)

    如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。

    信号量是一个计数器,用于控制访问有限共享资源的线程数。    

 1 // 创建信号量
 2 int sem_init (sem_t* sem, int pshared,unsigned int value);
 3 sem     - 信号量ID,输出。
 4 pshared - 一般取0,表示调用进程的信号量。
 5           非0表示该信号量可以共享内存的方式,
 6           为多个进程所共享(Linux暂不支持)。
 7 value   - 信号量初值。
 8 
 9 // 信号量减1,不够减即阻塞
10 int sem_wait (sem_t* sem);
11 
12 // 信号量减1,不够减即返回-1,errno为EAGAIN
13 int sem_trywait (sem_t* sem);
14 
15 // 信号量减1,不够减即阻塞,
16 // 直到abs_timeout超时返回-1,errno为ETIMEDOUT
17 int sem_timedwait (sem_t* sem,
18 const struct timespec* abs_timeout);
19 
20 struct timespec {
21     time_t tv_sec;  // Seconds
22     long   tv_nsec; // Nanoseconds [0 - 999999999]
23 };
24 
25 // 信号量加1
26 int sem_post (sem_t* sem);
27 
28 // 销毁信号量
29 int sem_destroy (sem_t* sem);

    特点:
    信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作
   (大家都在semtake的时候,就阻塞在哪里)

   三、条件变量(cond)

    条件变量可以让调用线程在满足特定条件的情况下暂停。

int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);
//亦可pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 使调用线程睡入条件变量cond,同时释放互斥锁mutex
int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);

int pthread_cond_timedwait (pthread_cond_t* cond,
    pthread_mutex_t* mutex,
    const struct timespec* abstime);

struct timespec {
    time_t tv_sec;  // Seconds
    long   tv_nsec; // Nanoseconds [0 - 999999999]
};

// 从条件变量cond中唤出一个线程,
// 令其重新获得原先的互斥锁
int pthread_cond_signal (pthread_cond_t* cond);

注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,
但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。

// 从条件变量cond中唤出所有线程
int pthread_cond_broadcast (pthread_cond_t* cond);

int pthread_cond_destroy (pthread_cond_t* cond);

   四、

 

posted @ 2019-10-12 17:27  一场雨  阅读(5878)  评论(0编辑  收藏  举报