linux基础_linux线程间通信及同步机制总结


linux基础——linux线程间通信及同步机制总结

线程间的通信有两种情况:

  1、一个进程中的线程与另外一个进程中的线程通信,由于两个线程只能访问自己所属进程的地址空间和资源,故等同于进程间的通信。

  2、同一个进程中的两个线程进行通信。本文说的就是第二种情况。

关于进程间通信(IPC)可以看我的另一篇博文

http://blog.csdn.net/a987073381/article/details/52006729

  比起进程复杂的通信机制(管道、匿名管道、消息队列、信号量、共享内存、内存映射以及socket等),线程间通信要简单的多。

  因为同一进程的不同线程共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段,所以线程之间可以方便、快速地共享信息。只需要将数据复制到共享(全局或堆)变量中即可。不过,要避免出现多个线程试图同时修改同一份信息

下图为多线程的进程地址空间:

 线程安全:

  所在的进程中有多个线程在同时运行,而这些线程可能会同时某一段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全就是说多线程访问同一段代码不会产生不确定的结果。编写线程安全的代码依靠线程同步。

线程间的同步:

  如果变量只读时,多个线程同时读取该变量不会有一致性问题,但是,当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。

 1. 互斥锁

  互斥量本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行枷锁以后,其他视图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程就会看到互斥量依然是锁着,只能再次阻塞等待它重新变成可用,这样,一次只有一个线程可以向前执行。

常用头文件:

#include <pthread.h>

常用函数:

  int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);//互斥初始化

  int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥

  int pthread_mutex_lock(pthread_mutex_t *mutex);//锁定互斥

  int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁互斥

  int pthread_mutex_trylock(pthread_mutex_t *mutex);//销毁互斥

  eg.pthread_t mutex;

  pthread_mutex_init(&mutex, NULL);

  pthread_mutex_lock(&mutex);

  ...

  pthread_mutex_unlock(&mutex);

  pthread_mutex_detroy(&mutex);

互斥量的死锁:

  一个线程需要访问两个或者更多不同的共享资源,而每个资源又有不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就可能发生死锁。死锁就是指多个线程/进程因竞争资源而造成的一种僵局(相互等待),若无外力作用,这些进程都将无法向前推进。

死锁的处理策略:

1、预防死锁:破坏死锁产生的四个条件:互斥条件、不剥夺条件、请求和保持条件以及循环等待条件。

2、避免死锁:在每次进行资源分配前,应该计算此次分配资源的安全性,如果此次资源分配不会导致系统进入不安全状态,那么将资源分配给进程,否则等待。算法:银行家算法。

3、检测死锁:检测到死锁后通过资源剥夺、撤销进程、进程回退等方法解除死锁。

 2. 读写锁

  读写锁与互斥量类似,不过读写锁拥有更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有3种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。/* 读写锁有3种状态:读模式下加锁状态,写模式下加锁状态,不加锁态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。*/

  当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。

常用头文件:

#include <pthread.h>

常用函数:

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

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

  int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式锁定读写锁

  int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式锁定读写锁

  int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁读写锁

  eg.pthread_rwlock_t q_lock;

  pthread_rwlock_init(&q_lock, NULL);

  pthread_rwlock_rdlock(&q_lock);

  ...

  pthread_rwlock_unlock(&q_lock);

  pthread_rwlock_detroy(&q_lock);

3. 条件变量

  条件变量是线程可用的另一种同步机制。互斥量用于上锁,条件变量则用于等待,并且条件变量总是需要与互斥量一起使用,运行线程以无竞争的方式等待特定的条件发生。/* 运行线程以无竞争的方式等待特定的条件发生;无竞争的方式等待是什么意思?*/

  条件变量本身是由互斥量保护的,线程在改变条件变量之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种变化因为互斥量必须在锁定之后才能计算条件。/*互斥量必须在锁定之后才能计算条件*/

常用头文件:

#include <pthread.h>

常用函数:

  int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);//初始化条件变量

  int pthread_cond_destroy(pthread_cond_t *cond);//销毁条件变量

  int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//无条件等待条件变量变为真

  int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *tsptr);//在给定时间内,等待条件变量变为真

  eg.pthread_mutex_t mutex;

  pthread_cond_t cond;

  ...

  pthread_mutex_lock(&mutex);

  pthread_cond_wait(&cond, &mutex);

  ...

  pthread_mutex_unlock(&mutex);

  ...

  注意:  pthread_cond_wait 执行的流程首先将这个mutex解锁然后等待条件变量被唤醒如果没有被唤醒该线程将一直休眠也就是说该线程将一直阻塞在这个pthread_cond_wait调用中, [A1] 而当此线程被唤醒时, 将自动将这个mutex加锁,然后再进行条件变量判断(原因是“惊群效应”,如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。),如果满足,则线程继续执行。/*pthread_cond_wait 执行的流程首先将这个mutex解锁,然后等待条件变量被唤醒,如果没有被唤醒,该线程将一直休眠,也就是说,该线程将一直阻塞在这个pthread_cond_wait调用中。*/

4. 信号量

  线程的信号和进程的信号量类似,使用线程的信号量可以高效地完成基于线程的资源计数。信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制。在公共资源增加的时候,信号量就增加;公共资源减少的时候,信号量就减少;只有当信号量的值大于0的时候,才能访问信号量所代表的公共资源。[A1] /* 在公共资源增加的时候,信号量就增加;公共资源减少的时候,信号量就减少;只有当信号量的值大于0的时候,才能访问信号量所代表的公共资源。*/

常用头文件:

#include <semaphore.h>

常用函数:

  sem_t sem_event;

  int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一个信号量

  int sem_destroy(sem_t * sem);//销毁信号量

  int sem_post(sem_t * sem);//信号量增加1

  int sem_wait(sem_t * sem);//信号量减少1

  int sem_getvalue(sem_t * sem, int * sval);//获取当前信号量的值 

互斥与同步的区别:

  互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

  同步:主要是流程上的概念,是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

 

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

  互斥锁:互斥,一个线程占用了某个资源,那么其它的线程就无法访问,直到这个线程解锁,其它线程才可以访问。

  条件变量:同步,一个线程完成了某一个动作就通过条件变量发送信号告诉别的线程,别的线程再进行某些动作。条件变量必须和互斥锁配合使用。

  信号量:同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。

 

另外还有以下几点需要注意:

  1、信号量可以模拟条件变量,因为条件变量和互斥量配合使用,相当于信号量模拟条件变量和互斥量的组合。在生产者消费者线程池中,生产者生产数据后就会发送一个信号 pthread_cond_signal通知消费者线程消费者线程通过pthread_cond_wait等待到了信号就可以继续执行。这是用条件变量和互斥锁实现生产者消费者线程的同步,用信号量一样可以实现!

  2、信号量可以模拟互斥量,因为互斥量只能为加锁或解锁(0 or 1),信号量值可以为非负整数,也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量时,就完成一个资源的互斥访问。前面说了,信号量主要用做多线程多任务之间的同步,而同步能够控制线程访问的流程,当信号量为单值时,必须有线程释放,其他线程才能获得,同一个时刻只有一个线程在运行(注意,这个运行不一定是访问资源,可能是计算)。如果线程是在访问资源,就相当于实现了对这个资源的互斥访问。

  3、互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性。

  4、互斥锁,条件变量都只用于同一个进程的各线程间而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。

  5、互斥量必须由同一线程获取以及释放信号量和条件变量则可以由一个线程释放,另一个线程得到。

  6、信号量的递增和减少会被系统自动记住,系统内部的计数器实现信号量,不必担心丢失,而唤醒一个条件变量时,如果没有相应的线程在等待该条件变量,此次唤醒会被丢失。

5. 自旋锁

  自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可以用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。

6. 屏障

  屏障是指用户可以协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从改点继续执行。

还记pthread_join函数吗?在子线程退出之前,主线程要一直等待。pthread_join函数就是一种屏障,它允许一个线程等待,直到另一个线程退出。屏障允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作。

  如果我们要让主线程在所有工作线程完成之后再做某项任务,一般把屏障计数值设为工作线程数加1,主线程也作为其中一个候选线程。

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Linux进程间通信与线程间同步详解(全面详细)

linux下进程间通信的几种主要手段简介:

     1. 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;[A1] /* 管道(pipe用于具有亲缘关系进程的通信;有名管道(name pipe除具有管道所具有的功能外,还允许无亲缘关系进程间的通信。*/

    2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数 sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal 函数);

     3. 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

     4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

     5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

     6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

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

1)互斥锁(mutex)

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

  int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);

  int pthread_mutex_lock(pthread_mutex *mutex);

  int pthread_mutex_destroy(pthread_mutex *mutex);

  int pthread_mutex_unlock(pthread_mutex *

(1)先初始化锁init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER

attr_t有:

  PTHREAD_MUTEX_TIMED_NP:其余线程等待队列

  PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争

  PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;

  PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争

(2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY

(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁

(4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用内存资源

示例代码

#include <cstdio>

#include <cstdlib>

#include <unistd.h>

#include <pthread.h>

#include "iostream"

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int tmp;

void* thread(void *arg)

{

    cout << "thread id is " << pthread_self() << endl;

    pthread_mutex_lock(&mutex);

    tmp = 12;

    cout << "Now a is " << tmp << endl;

    pthread_mutex_unlock(&mutex);

    return NULL;

}

int main()

{

    pthread_t id;

    cout << "main thread id is " << pthread_self() << endl;

    tmp = 3;

    cout << "In main func tmp = " << tmp << endl;

    if (!pthread_create(&id, NULL, thread, NULL))

    {

        cout << "Create thread success!" << endl;

    }

    else

    {

        cout << "Create thread failed!" << endl;

    }

    pthread_join(id, NULL);

    pthread_mutex_destroy(&mutex);

    return 0;

}

编译: g++ -o thread testthread.cpp -lpthread

说明:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在使用pthread_create()创建线程,以及调用pthread_atfork()函数建立fork处理程序时,需要链接该库。在编译中要加 -lpthread参数。[A1] /*线程处理函数是什么时候调用?*/

2)条件变量(cond)

    利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。

  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

对于

  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);

一定要在mutex的锁定区域内使用。

   如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考

pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!

  另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

说明:

  (1)pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。

    (2)互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。

      (3)pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁,且pthread_cond_timedwait返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970年1月1日00:00:00 GMT。

     (4)pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。

     (5)条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或pthread_cond_boardcast 函数,可能导致调用线程死锁。

示例程序1

#include <stdio.h>

#include <pthread.h>

#include "stdlib.h"

#include "unistd.h"

pthread_mutex_t mutex;

pthread_cond_t cond;

void hander(void *arg)

{

    free(arg);

    (void)pthread_mutex_unlock(&mutex);

}

void *thread1(void *arg)

{

     pthread_cleanup_push(hander, &mutex);

     while(1)

     {

         printf("thread1 is running\n");

         pthread_mutex_lock(&mutex);

         pthread_cond_wait(&cond,&mutex);

         printf("thread1 applied the condition\n");

         pthread_mutex_unlock(&mutex);

         sleep(4);

     }

     pthread_cleanup_pop(0);

}

void *thread2(void *arg)

{

    while(1)

    {

        printf("thread2 is running\n");

        pthread_mutex_lock(&mutex);

        pthread_cond_wait(&cond,&mutex);

        printf("thread2 applied the condition\n");

        pthread_mutex_unlock(&mutex);

        sleep(1);

    }

}

int main()

{

     pthread_t thid1,thid2;

     printf("condition variable study!\n");

     pthread_mutex_init(&mutex,NULL);

     pthread_cond_init(&cond,NULL);

     pthread_create(&thid1,NULL,thread1,NULL);

     pthread_create(&thid2,NULL,thread2,NULL);

     sleep(1);

     do

     {

         pthread_cond_signal(&cond);

     }while(1);

     sleep(20);

     pthread_exit(0);

     return 0;

}

示例程序2:

#include <pthread.h>

#include <unistd.h>

#include "stdio.h"

#include "stdlib.h"

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

struct node

{

     int n_number;

     struct node *n_next;

} *head = NULL;

/*[thread_func]*/

static void cleanup_handler(void *arg)

{

     printf("Cleanup handler of second thread./n");

     free(arg);

     (void)pthread_mutex_unlock(&mtx);

}

static void *thread_func(void *arg)

{

     struct node *p = NULL;

     pthread_cleanup_push(cleanup_handler, p);

     while (1)

     {

         //这个mutex主要是用来保证pthread_cond_wait的并发性

         pthread_mutex_lock(&mtx);

         while (head == NULL)

        {

        //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何

        //这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线

        //程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。

        //这个时候,应该让线程继续进入pthread_cond_wait

        // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,

        //然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立

        //而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源

        //用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/

         pthread_cond_wait(&cond, &mtx);

         p = head;

         head = head->n_next;

         printf("Got %d from front of queue/n", p->n_number);

         free(p);

       }

          pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁

     }

     pthread_cleanup_pop(0);

     return 0;

}

int main(void)

{

     pthread_t tid;

     int i;

     struct node *p;

     //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大

     pthread_create(&tid, NULL, thread_func, NULL);

     sleep(1);

     for (i = 0; i < 10; i++)

     {

         p = (struct node*)malloc(sizeof(struct node));

         p->n_number = i;

         pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,

         p->n_next = head;

         head = p;

         pthread_cond_signal(&cond);

         pthread_mutex_unlock(&mtx); //解锁

         sleep(1);

     }

     printf("thread 1 wanna end the line.So cancel thread 2./n");

    

     //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出

     //线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。

     pthread_cancel(tid);

     pthread_join(tid, NULL);

     printf("All done -- exiting/n");

     return 0;

}

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);

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

示例代码:

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <pthread.h>

#include <semaphore.h>

#include <errno.h>  

#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}  

typedef struct _PrivInfo

{

  sem_t s1;

  sem_t s2;

  time_t end_time;

}PrivInfo;

   

static void info_init (PrivInfo* thiz);

static void info_destroy (PrivInfo* thiz);

static void* pthread_func_1 (PrivInfo* thiz);

static void* pthread_func_2 (PrivInfo* thiz);

   

int main (int argc, char** argv)

{

  pthread_t pt_1 = 0;

  pthread_t pt_2 = 0;

  int ret = 0;

  PrivInfo* thiz = NULL;

     

  thiz = (PrivInfo* )malloc (sizeof (PrivInfo));

  if (thiz == NULL)

  {

    printf ("[%s]: Failed to malloc priv./n");

    return -1;

  }

   

  info_init (thiz);

  ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);

  if (ret != 0)

  {

    perror ("pthread_1_create:");

  }

   

  ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);

  if (ret != 0)

  {

     perror ("pthread_2_create:");

  }

   

  pthread_join (pt_1, NULL);

  pthread_join (pt_2, NULL);

   

  info_destroy (thiz);

  return 0;

}

   

static void info_init (PrivInfo* thiz)

{

  return_if_fail (thiz != NULL);

  thiz->end_time = time(NULL) + 10;

  sem_init (&thiz->s1, 0, 1);

  sem_init (&thiz->s2, 0, 0);

  return;

}

   

static void info_destroy (PrivInfo* thiz)

{

  return_if_fail (thiz != NULL);

  sem_destroy (&thiz->s1);

  sem_destroy (&thiz->s2);

  free (thiz);

  thiz = NULL;

  return;

}

static void* pthread_func_1 (PrivInfo* thiz)

{

  return_if_fail (thiz != NULL);

  while (time(NULL) < thiz->end_time)

  {

    sem_wait (&thiz->s2);

    printf ("pthread1: pthread1 get the lock./n");

   

    sem_post (&thiz->s1);

    printf ("pthread1: pthread1 unlock/n");

   

    sleep (1);

  } 

  return;

}

static void* pthread_func_2 (PrivInfo* thiz)

{

  return_if_fail (thiz != NULL);

  while (time (NULL) < thiz->end_time)

  {

    sem_wait (&thiz->s1);

    printf ("pthread2: pthread2 get the unlock./n");

    sem_post (&thiz->s2);

    printf ("pthread2: pthread2 unlock./n");

   

    sleep (1);

  }

   

  return;

}

  通过执行结果后,可以看出,会先执行线程二的函数,然后再执行线程一的函数。它们两就实现了同步。在上大学的时候,虽然对这些概念知道,可都没有实践过,所以有时候时间一久就会模糊甚至忘记,到了工作如果还保持这么一种状态,那就太可怕了。虽然现在外面的技术在不断的变化更新,可是不管怎么变,其核心技术还是依旧的,所以我们必须要打好自己的基础,再学习其他新的知识,那时候再学新的知识也会觉得比较简单的。

转自:https://blog.csdn.net/a987073381/article/details/52029070


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM