多线程----Pthreads

1.Pthreads简介(mac写的文章调布局不容易,请大家见谅,哈哈)

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。

2.Pthreads数据类型

pthreads的数据类型是结构体类型,里面包含了运行所需要的相关属性。

pthread_t:线程的标识符

pthread_attr_t:线程属性,主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。

pthread_barrier_t:同步屏障数据类型

pthread_mutex_t:mutex数据类型

pthread_cond_t:条件变量数据类型

pthread_key_t:线程私有存储类型

3.创建Pthreads线程

pthread_create():创建一个线程。

该函数包含4个参数:第一个参数是pthread_t *类型的指针;第二个参数是pthread_attr_t*类型的指针,切有const修饰不可更改;第三个参数是一个指针函数返回值是void *类型,我们只需传入函数地址即可(使用void *修饰地址);第四个参数是上面指针函数的参数;

返回值:0是成功,非0是失败;

4.Pthreads常用的线程操纵函数

pthread_detach(): 分离线程,使线程处于分离状态(unjoinable),一旦线程处于分离状态,该线程终止时底层资源立即被回收(系统自动回收)。

该函数包含一个参数:线程标识符;

返回值:0是成功,非0是失败;

pthread_join():阻塞当前的线程,直到另外一个线程运行结束。还可以用来显示的回收终止线程资源,当该函数返回值时线程才算真正意义上的结束(这种方式回收资源被认为是由另一个线程将该资源释放)

该函数包含2个参数:第一个参数即被连接线程的线程号;第二个参数比较复杂指,向一个指向被连接线程的返回码的指针的指针(一般写null);

返回值:0是成功,非0是失败;

这里有三点需要注意:

1.被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。

2.一个线程只能被一个线程所连接。

3.被连接的线程必须是非分离的,否则连接会出错。

pthread_join()有两种作用:

1.用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。

2.对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。

pthread_exit():终止调用它的线程。

该函数的参数为一个void*的指针,一般填写线程标识符的id;

注意:exit和pthread_exit以及return的不同

1、在主线程中,在main函数中return了或是调用了exit函数,则主线程退出,且整个进程也会终止,

此时进程中的所有线程也将终止。因此要避免main函数过早结束。

2、在主线程中调用pthread_exit,  则仅仅是主线程结束,进程不会结束,进程内的其他线程也不会结束,

知道所有线程结束,进程才会终止。

3、在任何一个线程中调用exit函数都会导致进程结束。进程一旦结束,那么进程中的所有线程都将结束

4、return,是函数返回,不一定是线程函数,只有线程函数return,线程才会退出

pthread_kill():第一个参数为pthread_t,第二个参数为int类型的数据。

向指定ID的线程发送sig信号,如果线程的代码内不做任何信号处理,则会按照信号默认的行为影响整个进程。也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出(慎用)。

pthread_kill(threadid, SIGKILL)也一样,他会杀死整个进程。

如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)。

所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。

那么,如果int sig的参数是0呢,这是一个保留信号,一个作用就是用来判断线程是不是还活着。

我们来看一下pthread_kill的返回值:

线程仍然活着:0

线程已不存在:ESRCH

信号不合法:EINVAL

pthread_cancel():请求中断另外一个线程的运行,属于被动结束线程。这种被动的结束分为两种,该方法向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略(当禁止取消时)、或者立即终止(当在取消点 或异步模式下)、或者继续运行至Cancelation-point(取消点,下面将描述),总之由不同的Cancelation状态决定,立即终结为异步终结,另外一种为同步终结。

一个线程处理cancel请求的退出操作相当于pthread_exit(PTHREAD_CANCELED)。当然线程可以通过设置为 PTHREAD_CANCEL_DISABLE来拒绝处理cancel请求,稍后会提及。

pthread_setcancelstate(int state, int *oldstate)设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。(了解)

pthread_setcanceltype(int type, int *oldtype)设置本线程取消动作的执行时机,type有两种取值:PTHREAD_CANCEL_DEFFERED 和 PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和 立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。(了解)

pthread_testcancel(void)检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

pthread_testcancel(void)用法如下:

#include <pthread.h>

#include <iostream>

#include <unistd.h>

using std::endl;

using std::cout;

void* test_cannel(void* arg)

{

  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);

  while(true)

  {

    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

    cout << "1" << endl;

    cout << "2" << endl;

    cout << "3" << endl;

    cout << "4" << endl;

    cout << "5" << endl;

    cout << "6" << endl;

    cout << "7" << endl;

    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

    pthread_testcancel();

  }

}

void* test_no_cannel(void* arg)

{

  //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);

  while(true)

  {

    cout << "1" << endl;

    cout << "2" << endl;

    cout << "3" << endl;

    cout << "4" << endl;

    cout << "5" << endl;

    cout << "6" << endl;

    cout << "7" << endl;

    //sleep(1);

  }

}

int main()

{

  pthread_t p1;

  pthread_create(&p1,NULL,test_cannel,NULL);

  sleep(1);

  pthread_cancel(p1);

  pthread_join(p1,NULL);

  return 0;

}

pthread_equal():比较线程ID,线程ID的大小没有意义。引入原因:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。

pthread_self()功能是获得线程自身的ID。(获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程,与gettid 获取的是内核中线程ID不同)。

注意:如果线程处于加锁、等待、阻塞(挂起状态)等状态最好先将相关状态取消,再进行线程的取消或退出,不然容易发生错误(可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃)。

解决方法:

首先介绍一下需要用到的函数:

pthread_cleanup_push(void(*routine) (void*),void*arg)第一个参数为函数名,第二个参数为前面函数的参数

pthread_cleanup_pop(int execute)参数为int类型的数字,以非0参数调用时,引起当前被弹出的线程清理程序执行。

这两个函数官方将的比较不好理解,以下是网上易懂的解释:

pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop之前异常退出(return是正常退出,其他是异常),那么系统就会执行这个回调函数(回调函数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,pthread_cleanup_pop就把对应的回调函数取消了。两个函数必须成对出现。

有三种情况线程清理函数会被调用:

线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消

线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止

线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.

注意:pthread有两种状态joinable状态和unjoinable状态 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit或pthread_cancel时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)结束相应子进程。

5.信号量

信号量机制通过信号量的值控制可用资源的数量。线程访问共享资源前,需要申请获取一个信号量,如果信号量为0,说明当前无可用的资源,线程无法获取信号量,则该线程会等待其他资源释放信号量(信号量加1)。如果信号量不为0,说明当前有可用的资源,此时线程占用一个资源,对应信号量减1。

举例:停车场有5个停车位,汽车可使用停车位。在这里5个停车位是共享的资源,汽车是线程。开始信号量为5,表明此时有5个停车位可用。一辆汽车进入停车场前,先查询信号量的值,不为0表明有可用停车位,汽车进入停车场并使用一个停车位,信号量减1,表明占用一个停车位,可用数减少。

初始化信号量:

int sem_init(sem_t *sem, int pshared, unsigned int val);

该函数第一个参数为信号量指针,第二个参数为信号量类型(一般设置为0),第三个为信号量初始值。第二个参数pshared为0时,该进程内所有线程可用,不为0时不同进程间可用。

信号量减1:

int sem_wait(sem_t *sem);

该函数申请一个信号量,若当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。

信号量加1:

int sem_post(sem_t *sem);

该函数释放一个信号量,信号量的值加1。

销毁信号量:

int sem_destory(sem_t *sem);

该函数销毁信号量。

案例:

        采用信号量机制,解决苹果橙子问题:一个能放N(这里N设为3)个水果的盘子,爸爸只往盘子里放苹果,妈妈只放橙子,女儿只吃盘子里的橙子,儿子只吃苹果。

        采用三个信号量:

        1.sem_t empty:信号量empty控制盘子可放水果数,初始为3,因为开始盘子为空可放水果数为3。

        2.sem_t  apple ;信号量apple控制儿子可吃的苹果数,初始为0,因为开始盘子里没苹果。

        3.sem_t orange;信号量orange控制女儿可吃的橙子是,初始为0,因为开始盘子里没橙子。

注:互斥量work_mutex只为printf输出时能够保持一致,可忽略。

#include

#pragma comment(lib, "pthreadVC2.lib")    //必须加上这句

sem_t empty;  //控制盘子里可放的水果数

sem_t apple;  //控制苹果数

sem_t orange; //控制橙子数

pthread_mutex_t work_mutex;                    //声明互斥量work_mutex

void *procf(void *arg) //father线程

          {

            while(1){

                sem_wait(&empty);    //占用一个盘子空间,可放水果数减1

                pthread_mutex_lock(&work_mutex);    //加锁

                printf("爸爸放入一个苹果!\n");

                sem_post(&apple);    //释放一个apple信号了,可吃苹果数加1

                pthread_mutex_unlock(&work_mutex);  //解锁

                Sleep(3000);

            }

          }

void *procm(void *arg)  //mother线程

          {

            while(1){

                sem_wait(&empty);

                pthread_mutex_lock(&work_mutex);    //加锁

                printf("妈妈放入一个橙子!\n");

                sem_post(&orange);

                pthread_mutex_unlock(&work_mutex);  //解锁

                Sleep(4000);

            }

          }

void *procs(void *arg)  //son线程

          {

            while(1){

                sem_wait(&apple);      //占用一个苹果信号量,可吃苹果数减1

                pthread_mutex_lock(&work_mutex);    //加锁

                printf("儿子吃了一个苹果!\n");

                sem_post(&empty);      //吃了一个苹果,释放一个盘子空间,可放水果数加1

                pthread_mutex_unlock(&work_mutex);  //解锁

                Sleep(1000);

            }

          }

void *procd(void *arg)  //daughter线程

          {

            while(1){

                sem_wait(&orange);

                pthread_mutex_lock(&work_mutex);    //加锁

                printf("女儿吃了一个橙子!\n");

                sem_post(&empty);

                pthread_mutex_unlock(&work_mutex);  //解锁

                Sleep(2000);

            }

          }

void main()

{

    pthread_t father;  //定义线程

    pthread_t mother;

    pthread_t son;

    pthread_t daughter;

    sem_init(&empty, 0, 3);  //信号量初始化

    sem_init(&apple, 0, 0);

    sem_init(&orange, 0, 0);

pthread_mutex_init(&work_mutex, NULL);  //初始化互斥量

    pthread_create(&father,NULL,procf,NULL);  //创建线程

    pthread_create(&mother,NULL,procm,NULL);

    pthread_create(&daughter,NULL,procd,NULL);

    pthread_create(&son,NULL,procs,NULL);

    Sleep(1000000000);

}

6.互斥量/锁

即对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。

要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。

互斥锁初始化有两种方式:

1.普通初始化

pthread_mutex_t mutex_t;//定义互斥锁变量(结构体)

pthread_mutex_init(&mutex_t,NULL);//初始化线程锁,第一个参数为pthread_mutex_t *类型的指针,第二个参数为互斥锁的属性

2.宏初始化

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

加锁:pthread_mutex_lock();//参数为pthread_mutex_t *类型的指针

解锁:pthread_mutex_unlock();//参数为pthread_mutex_t *类型的指针

尝试加锁:pthread_mutex_trylock();//参数为pthread_mutex_t *类型的指针,加锁成功则返回0,非零则是不成功

销毁线程锁:pthread_mutex_destroy ();//参数为pthread_mutex_t *类型的指针,销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。

锁的创建和销毁是对应的在适当的时候记得销毁线程锁。

实际操作案例如下:

#include<stdlib.h>

#include<stdio.h>

#include<unistd.h>

#include<pthread.h>

typedef struct ct_sum

{

    int sum;

    pthread_mutex_t lock;

}ct_sum;

void * add1(void *cnt)

{   

    pthread_mutex_lock(&(((ct_sum*)cnt)->lock));

    for(int i=0; i < 50; i++)

    {

        (*(ct_sum*)cnt).sum += i;   

    }

    pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));

    pthread_exit(NULL);

    return 0;

}

void * add2(void *cnt)

{     

    pthread_mutex_lock(&(((ct_sum*)cnt)->lock));

    for(int i=50; i<101; i++)

    {

        (*(ct_sum*)cnt).sum += i; 

    }

    pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));

    pthread_exit(NULL);

    return 0;

}


int main(void)

{

    pthread_t ptid1, ptid2;

    ct_sum cnt;

    pthread_mutex_init(&(cnt.lock), NULL);

    cnt.sum=0;


    pthread_create(&ptid1, NULL, add1, &cnt);

    pthread_create(&ptid2, NULL, add2, &cnt);


    pthread_join(ptid1,NULL);

    pthread_join(ptid2,NULL);

    printf("sum %d\n", cnt.sum);

    pthread_mutex_destroy(&(cnt.lock));

    return 0;

}

pthread_mutex_lock()和pthread_mutex_trylock()的区别:

pthread_mutex_lock()是阻塞调用,意思就是如果这个锁此时正在被其它线程占用, 那么pthread_mutex_lock() 调用会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁之后才会返回。

pthread_mutex_trylock()是非阻塞调用,当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁,如果加锁成功则返回0;

pthread_mutex_timedlock()的超时调用:

pthread_mutex_timedlock()也是阻塞调用,但它可以设置超时,不会长时间等待。如下例子,超过1秒未加锁就会返回错误信息。

struct timespec

{

    time_t tv_sec;        /* Seconds.  */

    long int tv_nsec;      /* Nanoseconds.  */

};

struct timespec abs_timeout;

abs_timeout.tv_sec = time(NULL) + 1;

abs_timeout.tv_nsec = 0;

int err = pthread_mutex_timedlock(&mtx, &abs_timeout);

if(0 != err) {

    if(ETIMEDOUT == err) {

        //The mutex could not be locked before the specified timeout expired.

    }

}

7.条件锁

条件变量机制弥补了互斥机制的缺陷,允许一个线程向另一个线程发送信号(这意味着共享资源某种条件满足时,可以通过某个线程发信号的方式通知等待的线程),允许阻塞等待线程(当线程等待共享资源某个条件时,可让该线程阻塞,等待其他线程发送信号通知)。

条件变量机制在处理等待共享资源满足某个条件问题时,具有非常高的效率,且空间消耗相比互斥机制也有优势。

条件变量机制,所有等待一个条件变量的线程会形成一个队列,这个队列显然是全局的共享队列。在调用pthread_cond_wait前加锁互斥量【必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP)】。

传入前锁mutex是为了保证线程从条件判断(我的理解是防止提前唤醒)到进入pthread_cond_wait前,条件不被改变。

如果没有传入前的锁。就会有这样的情况:线程A判断条件不满足之后,调用pthread_cond_wait之前,A休眠。线程B更改了条件,使得条件满足,但此时线程A还没有调用pthread_cond_wait。等到线程A又启动调用pthread_cond_wait后虽然条件满足,但却收不到pthread_cond_signal的唤醒,就一直阻塞下去。

传入后解锁是为了条件能够被改变

传入后的解锁,是因为调用pthread_cond_signal的那部分,需要先加锁更改条件后才调用pthread_cond_signal。(更改条件与等待条件满足,都是针对条件这一个资源的竞争,所以调用pthread_cond_wait和调用pthread_cond_signal的两个线程需要同一把锁)如果pthread_cond_wait内不对mutex解锁,那么在调用pthread_cond_wait后,其他线程就不能更改条件,条件就会一直不满足。

返回前再次锁mutex是为了保证线程从pthread_cond_wait返回后 到 再次条件判断前不被改变。

保证 在pthread_cond_signal之后与解锁mutex之间可能需要的其他语句能够执行

对于1,这里的理由与传入pthread_cond_wait前锁mutex的理由差不多。如果不锁,那么线程A调用pthread_cond_wait后,条件满足,线程A被唤醒,从pthread_cond_wait返回。线程B在此时更改了条件,使得条件不满足。线程A并不知道条件又被更改,还是以为条件满足,就可能出错。

对于2,只要在pthread_cond_signal之后与解锁mutex之间有其他语句需要执行,那么由于mutex在这时已经被这个线程锁,还没有解锁,所以调用pthread_cond_wait的那个线程在pthread_cond_wait返回前的锁mutex的行为就会阻塞,直到pthread_cond_signal后的语句执行完解锁,pthread_cond_wait才会返回。

说到这里就顺便说一下,由于pthread_cond_wait返回再次锁的行为,pthread_cond_signal不一定放在 lock()和unlock()中间。

pthread_cond_init() 该函数第一个参数为条件变量指针,第二个参数为条件变量属性指针(一般设为NULL)。该函数按照条件变量属性对条件变量进程初始化。

创建锁有两种方式:

1.普通初始化

pthread_cond_t cond_t;//定义互斥锁变量(结构体)

pthread_cond_init(&cond_t,NULL);//初始化线程锁,第一个参数为pthread_cond_t *类型的指针,第二个参数为互斥锁的属性

2.宏初始化

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_wait()该函数第一个参数为条件变量指针,第二个为互斥量指针。该函数调用前,需本线程加锁互斥量,加锁状态的时间内函数完成线程加入等待队列操作 ,线程进入等待前函数解锁互斥量。在满足条件离开pthread_cond_wait函数之前重新获得互斥量并加锁,因此,本线程之后需要再次解锁互斥量

pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);该函数第一个参数为条件变量指针,第二个为互斥量指针,第三个参数为时间类型的指针切不可更改。

abstime是一个绝对时间,struct timespce的原型为:

1struct timespec {2  time_t tv_sec;    /* Seconds */3long tv_nsec;    /* Nanoseconds */4};

其中tv_sec是秒,tv_nsec是纳秒(即1000,000,000分之一秒).

该函数的作用线程等待一定的时间,如果超时或有信号触发,线程唤醒。


pthread_cond_signal()该函数的参数为条件变量指针。该函数向队列第一个等待线程发送信号,解除这个线程的阻塞状态。

pthread_cond_broadcast()该函数的参数为条件变量指针。该函数想队列所有等待线程发送信号,解除这些线程的阻塞状态。

pthread_cond_destroy()该函数销毁条件变量。

#include<stdio.h>

#include<pthread.h>

#include<Windows.h>

#include<semaphore.h>

#pragmacomment(lib,"pthreadVC2.lib")//必须加上这句

pthread_tt1;//pthread_t变量t1,用于获取线程1的ID

pthread_tt2;//pthread_t变量t2,用于获取线程2的ID   

pthread_mutex_tmutex;

pthread_cond_tcond;

inti =0;//共享资源

void*child1(void*arg)

{

while(1)

{

pthread_mutex_lock(&mutex);

i++;

if(i %5==0)

{

pthread_cond_signal(&cond);

}

else

{

printf("我是线程  1  打印的数都非5的倍数:  %d \n", i);

}

pthread_mutex_unlock(&mutex);

Sleep(1000);

}

}

void*child2(void*arg)

{

while(1)

{

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);//获得信号之前,会重新获得互斥锁

printf("我是线程  2  打印5的倍数:  %d \n", i);

pthread_mutex_unlock(&mutex);//需要在此处释放互斥锁

Sleep(1000);

}

}

intmain(void)

{

pthread_cond_init(&cond,NULL);

pthread_mutex_init(&mutex,NULL);

pthread_create(&t1,NULL, child1,NULL);

pthread_create(&t2,NULL, child2,NULL);

Sleep(100000000);

}

8.读写锁

pthread读写锁把对共享资源的访问者分为读者和写者,读者只对共享资源进行读访问,写者只对共享资源进行写操作。在互斥机制,读者和写者都需要独立独占互斥量以独占共享资源,在读写锁机制下,允许同时有多个读者读访问共享资源,只有写者才需要独占资源。相比互斥机制,读写机制由于允许多个读者同时读访问共享资源,进一步提高了多线程的并发度。

  1.读写锁机制:

写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。

读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

2.读写锁特性:

同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。

读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。

读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁。

3.读写锁基本函数:

读写锁初始化: int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t *  attr);

该函数第一个参数为读写锁指针,第二个参数为读写锁属性指针。函数按读写锁属性对读写锁进行初始化。

加读锁:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

该函数参数为读写锁指针。函数用于对读写锁加读锁。

加写锁:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

该函数参数为读写锁指针。函数用于对读写锁加写锁。

释放读写锁:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

该函数参数为读写锁指针。函数用于释放读写锁,包括读锁与写锁。

销毁读写锁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

该函数参数为读写锁指针。函数用于销毁读写锁。

以下为读写锁属性(不详细讲了):

int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr)

int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);

都返回:成功时为0,出错时为正的Exxx值

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

都返回:成功时为0,出错时为正的Exxx值

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *valptr);

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int valptr);

都返回:成功时为0,出错时为正的Exxx值

4.案例:

示例使用读写锁,对共享资源data进行读写同步,线程readerM,readerN为读者线程,线程writerA,writerB为写者线程。

#include

#pragma comment(lib, "pthreadVC2.lib")    //必须加上这句

pthread_t t1;          //pthread_t变量t1,用于获取线程1的ID

pthread_t t2;          //pthread_t变量t2,用于获取线程2的ID

pthread_rwlock_t rwlock;            //声明读写锁

int data=1;                          //共享资源

void* readerM(void* arg)

{

while(1)

{

pthread_rwlock_rdlock(&rwlock);    //读者加读锁

printf("M 读者读出: %d \n",data);  //读取共享资源

pthread_rwlock_unlock(&rwlock);    //读者释放读锁

Sleep(1200);

}

return NULL;

}

void* readerN(void* arg)

{

while(1)

{

pthread_rwlock_rdlock(&rwlock);

printf(" N读者读出: %d \n",data);

pthread_rwlock_unlock(&rwlock);

Sleep(700);

}

return NULL;

}

void* writerA(void* arg)

{

while(1)

{

pthread_rwlock_wrlock(&rwlock);      //写者加写锁

data++;                              //对共享资源写数据

printf(" A写者写入: %d\n",data);

pthread_rwlock_unlock(&rwlock);      //释放写锁

Sleep(2000);

}

return NULL;

}

void* writerB(void* arg)

{

while(1)

{

pthread_rwlock_wrlock(&rwlock);

data++;

printf(" B写者写入: %d\n",data);

pthread_rwlock_unlock(&rwlock);

Sleep(2000);

}

return NULL;

}

void main(int argc,char** argv)

{

pthread_rwlock_init(&rwlock, NULL);  //初始化读写锁

pthread_create(&t1,NULL,readerM,NULL);

pthread_create(&t1,NULL,readerN,NULL);

pthread_create(&t2,NULL,writerA,NULL);

pthread_create(&t2,NULL,writerB,NULL);

pthread_rwlock_destroy(&rwlock);      //销毁读写锁

Sleep(10000000);

return;

}

9.线程私有存储

所有线程共享程序中的变量。现在有一全局变量,所有线程都可以使用它,改变它的值。而如果每个线程希望能单独拥有它,那么就需要使用线程存储了。表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。

下面说一下线程存储的具体用法。

pthread_key_create()用来创建存储所需要的环境。该函数有两个参数,第一个参为pthread_key_t *的指针变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成NULL,这样系统将调用默认的清理函数。该函数成功返回0,其他任何返回值都表示出现了错误。

当线程中需要存储特殊值的时候,可以调用pthread_setspcific()。该函数有两个参数,第一个为前面声明的pthread_key_t类型的变量,第二个为void*变量,放任意类型数据的指针。

pthread_key_delete()销毁线程特定数据键,参数为pthread_key_t类型的变量。

如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。下面是前面提到的函数的原型:

#include<stdio.h>

#include

#include

#include

pthread_key_t key;

structtest_struct {

    inti;

    floatk;

};

void*child1(void*arg)

{

    structtest_structstruct_data;

    struct_data.i=10;

    struct_data.k=3.1415;

    pthread_setspecific(key, &struct_data);

    printf("child1--address of struct_data is --> 0x%p\n", &(struct_data));

    printf("child1--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (struct test_struct *)pthread_getspecific(key));

    printf("child1--from pthread_getspecific(key) get the pointer and print it's content:\nstruct_data.i:%d\nstruct_data.k: %f\n",

        ((struct test_struct *)pthread_getspecific(key))->i, ((struct test_struct *)pthread_getspecific(key))->k);

    printf("------------------------------------------------------\n");

}

void*child2(void*arg)

{

    inttemp =20;

    sleep(2);

    printf("child2--temp's address is 0x%p\n", &temp);

    pthread_setspecific(key, &temp);

    printf("child2--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (int *)pthread_getspecific(key));

    printf("child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:%d\n", *((int *)pthread_getspecific(key)));

}

int main(void)

{

    pthread_ttid1, tid2;

    pthread_key_create(&key, NULL);

    pthread_create(&tid1,NULL,child1,NULL);

    pthread_create(&tid2,NULL,child2,NULL);

    pthread_join(tid1,NULL);

    pthread_join(tid2,NULL);

    pthread_key_delete(key);

    return(0);

}

输出结果:

child1--address of struct_data is --> 0x0x7ffff77eff40

child1--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff77eff40

child1--from pthread_getspecific(key) get the pointer and print it's content:

struct_data.i:10

struct_data.k: 3.141500

--------------------------------------------

child2--temp's address is 0x0x7ffff6feef44

child2--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff6feef44

child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:20

10.其他方法

pthread_once():某些需要仅执行一次的函数。其中第一个参数为pthread_once_t类型,是内部实现的互斥锁,保证在程序全局仅执行一次,第二个参数为需要执行的函数名字。

#include<iostream>

#include<pthread.h>

using namespace std;

pthread_once_t once = PTHREAD_ONCE_INIT;

void once_run(void)

{

        cout<<"once_run in thread "<<(unsigned int )pthread_self()<<endl;

}

void * child1(void * arg)

{

        pthread_t tid =pthread_self();

        cout<<"thread "<<(unsigned int )tid<<" enter"<<endl;

        pthread_once(&once,once_run);

        cout<<"thread "<<tid<<" return"<<endl;

}

void * child2(void * arg)

{

        pthread_t tid =pthread_self();

        cout<<"thread "<<(unsigned int )tid<<" enter"<<endl;

        pthread_once(&once,once_run);

        cout<<"thread "<<tid<<" return"<<endl;

}

int main(void)

{

        pthread_t tid1,tid2;

        cout<<"hello"<<endl;

        pthread_create(&tid1,NULL,child1,NULL);

        pthread_create(&tid2,NULL,child2,NULL);

        sleep(10);

        cout<<"main thread exit"<<endl;

        return 0;

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352