linux 多线程编程笔记

一, 线程基础知识

1,线程的概念

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行

中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

2,线程的优点
(1)       通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。
(2)       多个线程可以自动共享相同的存储地址空间和文件描述符。
(3)       有些问题可以通过将其分解从而改善整个程序的吞吐量。
(4)       交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中处理用户输入输出的部分与其它部分分开。

3,线程的缺点
     线程也有不足之处。编写多线程程序需要更全面更深入的思考。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良

     影响的可能性是很大的。调试一个多线程程序也比调试一个单线程程序困难得多。

4,线程的结构
     线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽子,errno变量以及线

   程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存、栈以及文件描述符。

   进程ID在整个系统中是唯一的,但线程不同,线程ID只在它所属的进程环境中有效。

5,线程的创建
 使用pthread_create函数。

#include<pthread.h>
int pthread_create (pthread_t *__restrict tidp,//新创建的线程ID
			   __const pthread_attr_t *__restrict __attr,//线程属性
			   void *(*__start_routine) (void *),//新创建的线程从start_routine开始执行
			   void *__restrict __arg)//执行函数的参数


 当pthread_creat成功返回时, tidp指向的内存单元被设置为新创建线程的线程ID。_attr参数用于定制各种不同的线程属性。可以把它设置为NULL,创建默认的线程属性。

新创建的线程从__start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向__start_routine函数传递的参数不止一个,那么需要把这些参数放到

一个结构中,然后把这个结构的地址作为arg参数传入。返回值:成功-0,失败-返回错误编号,可以用strerror(errno)函数得到错误信息.

6,.线程的终止
       线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。

(1)线程只是从启动例程中返回,返回值是线程的退出码。

(2)线程可以被同一进程中的其他线程取消。

(3)线程调用pthread_exit.

 7,终止线程, 怎样正确处理线程终止。


  调用 pthread_exit

#include <pthread.h>
void pthread_exit(void *rval_ptr);

rval_prt是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以调用pthread_join函数访问到这个指针。

 

  在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是

相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一

个线程的终止状态并且释放该线程所占的资源。

pthread_join:获得进程的终止状态

#include <pthread.h>
int pthread_join(pthread_t thread,void **rval_ptr);

返回值:若成功返回0,否则返回错误编号。


当一个线程通过调用pthread_exit退出或者简单地从启动历程中返回时,进程中的其他线程可以通过调用pthread_join函数获得进程的退出状态。调用pthread_join进程

将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。如果线程只是从它的启动历程返回,rval_ptr将包含返回码。(此时线程应该出现非分离状态)。

在默认情况下,线程的终止状态会保存到对该线程调用pthread_join(即默认情况下线程处于非分离状态),如果线程已经处于分离状态,线程的底层存储资源可以在线程终

止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL.

如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为

detached 状态可以通过两种方式来实现。

一种是调用 pthread_detach() 函数,可以将线程 设置为 detached 状态。

 pthread_detach:使线程进入分离状态。

#include <pthread.h>
int pthread_detach(pthread_t tid);

返回值:若成功则返回0,否则返回错误编号。

另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。方法如下代码:

 

    pthread_t       tid;
    pthread_attr_t  attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
    ...

    pthread_attr_destroy(&attr);


总之为了在使用 Pthread 时 避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于

 

detached状态,否则就需要调用 pthread_join() 函数来对其进行资源回收

好了,说这么多,举个例子先。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#define THREADS 2 // 线程个数

int num = 0;
void *run(void *arg)// 线程执行函数,线程0执行五次减,线程1执行五次加
{
    int flag = (int)arg;
    if(flag %2) // 根据线程参数执行相应动作
    {
      for(int i = 0; i < 5; i++)
      {
        num++;
        printf("running in thread %d, num: %d\n", flag, num);
      }
    }
    else
    {
      for(int i = 0; i < 5; i++)
      {
          num--;
          printf("running in thread %d, num: %d\n", flag, num);
      }
    }
    return (void *)arg;
}
int main()
{
    int i,err;
    void * ret;
    pthread_t tid[THREADS];
    for(i = 0; i < THREADS; i++) // 创建线程
    {
        if(0 != (err = pthread_create(&tid[i], NULL, run, (void *)i)))
        {
            printf("thread create error %s\n",strerror(err));
            exit(-1);
        }
    }

    for(i = 0; i < THREADS; i++)阻塞等待线程,直到该线程退出 
    {
        if(0 != (err = pthread_join(tid[i], &ret)))
        {
            printf("thread join error %s\n",strerror(err));
            exit(-1);
        }
        else
        {
            printf("thread %d exit code %d\n", i, (int)ret); // 打印线程退出代码
        }
    }
    return 0;
}

编译 此程序别忘了加上加上-lpthread参数。pthread库不是linux默认的库,所以在编译时候需要指明libpthread.a库。

g++  -o  thread_test  thread_test.c  -lpthread

程序执行结果:

两个线程分别执行自减 5次 自加 5次最后结果为0,我们可以看到 thread 1最后输出为 0 结果是对的。但是仔细看所有的输出,你会发现有异样的东西。

thread 0 怎么输出连续2个 -1,thread1 怎么输出0然后直接 输出-3 。原因是两个线程可以同时操作 num 。 而且 num--(++),printf 不是一个原子操作 。

比如:当thread0 执行时 设此时num=-1 ,然后执行 num--, 此时 num的值变为 -2 而此时还没打印 printf  num的值 转而线程thread执行了num++,此时

num的值又变回 -1,所以会出现打印连续两个 -1 。

 当多个线程对共享区域进行修改时,应该采用同步的方式 才能达到我们有时候的需要,后面再叙述。


二,线程高级知识

1,线程属性

 线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化,在使用后需要对其去除初始化。我们用pthread_attr_init函数对其初始化,用

 pthread_attr_destroy对其去除初始化。

线程属性结构如下:

typedef struct
{
       int                               detachstate;   //线程的分离状态
       int                               schedpolicy;  //线程调度策略
       struct sched_param              schedparam;  //线程的调度参数
       int                               inheritsched;  //线程的继承性
       int                                scope;       //线程的作用域
       size_t                           guardsize;   //线程栈末尾的警戒缓冲区大小
       int                                stackaddr_set;//堆栈地址集
       void *                          stackaddr;   //线程栈的位置
       size_t                           stacksize;    //线程栈的大小
}pthread_attr_t;


属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性值不能直接设置,每个属性都对应一些函数对其查看或修改。初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。

线程属性修改函数介绍如下:

A,线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,只有当pthread_join()函数返回时,

创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,

马上释放系统资源。

获取/修改线程的分离状态属性:

int pthread_attr_getdetachstate(const pthread_attr_t * attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);

参数: attr   线程属性变量 detachstate  线程的分离状态属性 ,返回值: 若成功返回0,若失败返回-1。

可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:

设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程。

可以使用pthread_attr_getdetachstate函数获取当前的datachstate线程属性。

 

B,线程的继承性

函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设置和得到线程的继承性,这两个函数的定义如下:

#include <pthread.h>
Int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

参数: attr 线程属性变量 inheritsched   线程的继承性  返回值:若成功返回0,若失败返回-1。

 这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是继承性或指向继承性的指针。继承性决定调度的参数是从创建的进程中继承还是使用在

schedpolicy和schedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程的调度策略和参数,必须先设置该属性。
继承性的可能值是PTHREAD_INHERIT_SCHED(表示新线程将继承创建线程的调度策略和参数)和PTHREAD_EXPLICIT_SCHED(表示使用在schedpolicy

和schedparam属性中显式设置的调度策略和参数)。如果你需要显式的设置一个线程的调度策略或参数,那么你必须在设置之前将inheritsched属性设置为  THREAD_EXPLICIT_SCHED.

C,线程的调度策略

  函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用来设置和得到线程的调度策略。

#include <pthread.h>
int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);

参数:attr  线程属性变量policy 调度策略返回值:若成功返回0,若失败返回-1。

 这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是调度策略或指向调度策略的指针。调度策略可能的值是先进先出(SCHED_FIFO)、

轮转法(SCHED_RR),或其它(SCHED_OTHER)。SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。
在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。
SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时,运行的线程将被抢占以便准备好的线程可以执行。
当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级的SCHED_FIFO

线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。

D,线程的调度参数

函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别用来设置和得到线程的调度参数。

#include <pthread.h>
int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param *param);
int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);

参数: attr 线程属性变量 param   sched_param结构 返回值: 若成功返回0,若失败返回-1。


这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是sched_param结构或指向该结构的指针。结构sched_param在文件/usr/include /bits/sched.h中定义如下:

struct sched_param
{
       int sched_priority;
};


结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到。结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到。

注意:如果不是编写实时程序,不建议修改线程的优先级。因为,调度策略是一件非常复杂的事情,如果不正确使用会导致程序错误,从而导致死锁等问题。如:在多线程应用程序中为线程设置不同的优先级别,有可能因为共享资源而导致优先级倒置。

#include <pthread.h>
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

参数:policy  系统支持的线程优先权的最大和最小值 返回值:若成功返回0,若失败返回-1。

下面是有关线程属性的程序例子:

 

#include<pthread.h>
#include <unistd.h>
#include <sched.h>
#incude <stdio.h>
 
void * child_thread(void *arg)
{
 int policy;

int max_priority,min_priority;

struct sched_param param;

pthread_attr_t attr;

pthread_attr_init(&attr); /*初始化线程属性变量*/

pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);/*设置线程继承性*/

pthread_attr_getinheritsched(&attr,&policy); /*获得线程的继承性*/

if(policy==PTHREAD_EXPLICIT_SCHED)

    printf("Inheritsched:PTHREAD_EXPLICIT_SCHED\n");

if(policy==PTHREAD_INHERIT_SCHED)

    printf("Inheritsched:PTHREAD_INHERIT_SCHED\n");

 

pthread_attr_setschedpolicy(&attr,SCHED_RR);/*设置线程调度策略*/

pthread_attr_getschedpolicy(&attr,&policy);/*取得线程的调度策略*/

if(policy==SCHED_FIFO)

    printf("Schedpolicy:SCHED_FIFO\n");

if(policy==SCHED_RR)

    printf("Schedpolicy:SCHED_RR\n");

if(policy==SCHED_OTHER)

    printf("Schedpolicy:SCHED_OTHER\n");

sched_get_priority_max(max_priority);/*获得系统支持的线程优先权的最大值*/

sched_get_priority_min(min_priority);/* 获得系统支持的线程优先权的最小值*/

printf("Max priority:%u\n",max_priority);

printf("Min priority:%u\n",min_priority);

param.sched_priority=max_priority;

pthread_attr_setschedparam(&attr,¶m);/*设置线程的调度参数*/

printf("sched_priority:%u\n",param.sched_priority);/*获得线程的调度参数*/

pthread_attr_destroy(&attr);

}

int main( )

{
     pthread_t child_thread_id;
     pthread_create(&child_thread_id,NULL,child_thread,NULL);
     pthread_join(child_thread_id,NULL);
     return 0;
}


 

此外线程属性还有如下,不在仔细敷述:
 线程的作用域  函数pthread_attr_setscope和pthread_attr_getscope分别用来设置和得到线程的作用域
 线程堆栈的大小  函数pthread_attr_setstacksize和pthread_attr_getstacksize分别用来设置和得到线程堆栈的大小
 线程堆栈的地址   函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得到线程堆栈的位置
 线程栈末尾的警戒缓冲区大小  函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得到线程栈末尾的警戒缓冲区大小

 三,线程同步

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和异步信号。 

1,互斥锁

 什么是互斥锁

 

一种在多线程程序中同步访问手段是使用互斥量。程序员给某个对象加上一把“锁”,每次只允许一个线程去访问它。如果想对代码关键部分的访问进行控制,你必须在

进入这段代码之前锁定一把互斥量,在完成操作之后再打开它。

互斥锁函数有:

 

       pthread_mutex_init 初始化一个互斥量
       pthread_mutex_lock 给一个互斥量加锁
       pthread_mutex_trylock 加锁,如果失败不阻塞 
       pthread_mutex_destroy 销毁一个互斥量
       pthread_mutex_unlock 解锁   


   可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后

 

 释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所以

在该互斥锁上的阻塞线程都会变成可进行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程在次被阻塞,等待下次运行状态。

互斥量用pthread_mutex_t数据类型来表示

(1) 在使用互斥量以前,必须首先对它进行初始化pthread_mutex_init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER

 

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t * mutex,
const pthread_mutex_t *attr);

参数:mutex  互斥量 attr     互斥锁属性 返回值:若成功则返回0,否则返回错误编号。
 mutex 是我们要锁住的互斥量, attr 是互斥锁的属性,可用相应的函数修改:

 

attr_t有:
      PTHREAD_MUTEX_TIMED_NP:其余线程等待队列
      PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争
      PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;
      PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争
(2) 对互斥量加减锁

      对互斥量加/减锁:

 

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:若成功则返回0,否则返回错误编号。

 

对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程阻塞直至互斥量解锁

对互斥量解锁,需要调用pthread_mutex_unlock.

如果线程不希望被阻塞,他可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,

那么pthread_mutex_trylock将锁住互斥量,否则就会失败,不能锁住互斥量,而返回EBUSY

(3)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用资源内存
      释放对互斥变量分配的资源:

 

#include <pthread.h>
int  pthread_mutex_destroy(pthread_mutex_t *mutex);

返回值:若成功则返回0,否则返回错误编号。

好了,既然了解了互斥锁那么现在我们就 把 最开始的那个多线程的例子进行改装:

只需更改线程子函数,代码如下:

 

void *run(void *arg)
{
    int flag = (int)arg;
    if(flag %2)
    {
      for(int i = 0; i < 5; i++)
      {
        pthread_mutex_lock(&mylock);// 加锁
        num++;
        printf("running in thread %d, num: %d\n", flag, num);
        pthread_mutex_unlock(&mylock);// 解锁
      }
    }
    else
    {
      for(int i = 0; i < 5; i++)
      {
          pthread_mutex_lock(&mylock);// 加锁
          num--;
          printf("running in thread %d, num: %d\n", flag, num);
          pthread_mutex_unlock(&mylock);// 解锁
      }
    }
    return (void *)arg;
}

 

执行结果如下:

 

可以和先前的那个进行相应的比较,发现是不是用互斥量后程序正常了!


应用互斥量需要注意的几点:
 1、互斥量需要时间来加锁和解锁。锁住较少互斥量的程序通常运行得更快。所以,互斥量应该尽量少,够用即可,每个互斥量保护的区域应则尽量大。

  2、互斥量的本质是串行执行。如果很多线程需要领繁地加锁同一个互斥量,则线程的大部分时间就会在等待,这对性能是有害的。如果互斥量保护的

  数据(或代码)包含彼此无关的片段,则可以特大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待时间

  就会减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则应尽量的少。


2,条件变量(cond)

 什么是条件变量?

  与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

  条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作

  一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
条件的检测是在互斥锁的保护下进行的如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号

给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程

间的线程同步。

利用线程间共享的全局变量进行同步的一种机制。

 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);// 条件变量初始化    
 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);// 条件变量等待
 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
 int pthread_cond_destroy(pthread_cond_t *cond); // 条件变量摧毁
 int pthread_cond_signal(pthread_cond_t *cond);//在相同条件变量上阻塞的线程将被解锁,如果同时有多个线程阻塞,则由调度策略确定接收通知的线程
 int pthread_cond_broadcast(pthread_cond_t *cond);  // 解除所有线程的阻塞

(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL

 

(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真 timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)

(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)

(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

注意: 条件变量无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件

(Race Condition).mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()

 前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

举个例子先,利用2线程产生拥有连续10个AB的序列。

代码如下:

 

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define DE 1  // 是否debug
#define THREADS 2 // 线程个数

int num = 0;
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;//互斥量
pthread_cond_t ready = PTHREAD_COND_INITIALIZER;//条件变量 

void debug_p(char *str, int i)
{

#ifdef DE // 如果定义了debug则输出 调试信息
        printf("\nthread %d %s\n", i, str);
#endif
}
void *my_start(void *arg)//
{
    int id = (int)arg;
    char c = 'A' + id;
    int ret = -1,i = 0;
    for(; i < 10; i++)
    {
        pthread_mutex_lock(&mylock);//调用pthread_cond_wait前,必须获得互斥锁
        while(id != num)
        {
            debug_p("waitting", id);
            ret = pthread_cond_wait(&ready, &mylock);//把线程放入等待条件的线程列表,然后对互斥锁进行解锁,这两部都是原子操作。pthread_cond_wait返回时,互斥量再次锁住。  
            if(ret == 0)
              debug_p("wait success", id);
            else
              debug_p("wait failed", id);
        }
        printf("%c ", c);
        num = ++num%THREADS;
        pthread_mutex_unlock(&mylock);
        pthread_cond_broadcast(&ready);//唤醒等待该条件的所有线程 
    }
    return (void *)0;
}

int main(int argc, char* argv[])
{
    int i;
    pthread_t tid[THREADS];
    void *ret;
    for(i = 0; i < THREADS; i++)
    {
        if(pthread_create(&tid[i], 0, my_start ,(void *)i) != 0)
        {
            printf("threted create error!\n");
            exit(-1);
        }
    }
    for(i = 0; i < THREADS; i++)
    {
        if(pthread_join(tid[i], &ret) != 0)
        {
            printf("threted join error!\n");
            exit(-1);
        }
    }
   printf("\n");
    return 0;
}


可以看到,在当 #define 1 开启,即debug输出我们自定义的调试信息时,thread 0 刚开始满足条件输出A,num加1 解锁互斥量,

 

此时无等待该条件的线程。此时num=1 ,id=0 进入while循环 显然thread 0进入等待状态,然后执行thread 1 即此时 id=1,而此时

num也为1 此时跳过while循环输出 B。此时num++, thread 1进入 waititng,然后解锁互斥量,最后唤醒等待该条件的线程thread 0。

输出 A ....

注释掉// #define DE 即可输出 :ABABABABABABABABABAB


3, 信号量

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

信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。

int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。


 

条件变量与互斥锁、信号量的区别:

   到这里,我们把posix的互斥锁、信号量、条件变量都接受完了,下面我们来比较一下他们。
   a.互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,

       而另一个线程可以挂出该信号灯。

    b.互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。
    c.由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在

         该条件变量上,那么该信号将丢失。
    d.互斥锁是为了上锁而优化的,条件变量是为了等待而优化的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。


待续...


参考资料:

<APUE>

<UNP,第二卷>

http://blog.csdn.net/lanyan822/article/details/7587972

 

转载于:https://www.cnblogs.com/pangblog/p/3295283.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值