Unix/Linux编程:互斥锁与条件变量

本文介绍了互斥锁和条件变量的基本概念与使用方法,包括初始化、加锁解锁、超时锁、生产者消费者模式等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

保护对共享变量的访问:互斥锁

  1. 用于保护线程/进程的共享数据: 确保同一时间只有一个线程/进程能够访问数据
  • 互斥量上锁之后, 任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程解锁(释放互斥量)
  • 如果释放互斥量之后又一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程就可以对互斥量加锁,其他线程就会看到互斥量仍是锁着的,只能回去再次等待它重新变为可用
  1. 互斥锁是协作锁
  • 如果其中某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其他线程在使用线程之前都申请锁,也会出现线程安全问题

在这里插入图片描述

初始化

互斥量是属于 pthread_mutex_t 类型的变量。在使用之前必须对其初始化。

静态初始化

static pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;  //注意 PTHREAD_MUTEX_INITIALIZER不要省略(虽然PTHREAD_MUTEX_INITIALIZER被定义为0,而静态分配的变量会被自动初始化为0)

动态初始化

NAME
       pthread_mutex_destroy, pthread_mutex_init - 销毁并初始化互斥锁 
       
SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);

DESCRIPTION

		关于 attr, 表示互斥锁的属性。该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性.若将 attr 参
		数置为 NULL,则该互斥量的各种属性会取默认值。
		
		互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
		  * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
		  * PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
		  * PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
		  * PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

	  SUSv3 规定,初始化一个业已初始化的互斥量将导致未定义的行为,应当避免这一行为

      销毁未锁定的已初始化互斥锁应该是安全的。 尝试销毁锁定的互斥锁会导致未定义的行为。 

	  在默认互斥体属性合适的情况下,宏 PTHREAD_MUTEX_INITIALIZER 可用于初始化静态分配的互斥体。 除了不执行
	  错误检查外,效果应等同于通过调用 pthread_mutex_init() 并将参数 attr 指定为 NULL 进行动态初始化。 


返回值: 
		成功返回0; 失败返回错误编码

静态初始值 PTHREAD_MUTEX_INITIALIZER,只能用于对如下互斥量进行初始化:经由静态分配且携带默认属性。其他情况下,必须调用 pthread_mutex_init()对互斥量进行动态初始化

  • 动态分配于堆中的互斥量。例如,动态创建针对某一结构的链表,表中每个结构都包含一个 pthread_mutex_t 类型的字段来存放互斥量,借以保护对该结构的访问
  • 互斥量是在栈中分配的自动变量
  • 初始化经由静态分配,且不使用默认属性的互斥量。

当不再需要经由自动或动态分配的互斥量时,应使用 pthread_mutex_destroy()将其销毁。(对于使用 PTHREAD_MUTEX_INITIALIZER 静态初始化的互斥量,无需调用 pthread_mutex_destroy()。

只有当互斥量处于未锁定状态,且后续也无任何线程企图锁定它时,将其销毁才是安全的。若互斥量驻留于动态分配的一片内存区域中,应在释放(free)此内存区域前将其销毁。对于自动分配的互斥量,也应在宿主函数返回前将其销毁。

经由 pthread_mutex_destroy()销毁的互斥量,可调用 pthread_mutex_init()对其重新初始化

加锁和解锁

初始化之后,互斥量处于未锁定状态。函数 pthread_mutex_lock()可以锁定某一互斥量,而
函数 pthread_mutex_unlock()则可以将一个互斥量解锁

#include <pthread.h>

// 均返回:如果成功则返回0,如果出错则正的Exxx值
int pthread_mutex_lock(pthread_mutex_t *mutex);   // 上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试上锁
int pthread_mutex_unlock(pthreadd_mutex_t *mutex); // 解锁

pthread_mutex_lockpthread_mutex_trylock的区别:

  • 当某个进程/线程尝试给某一把锁上锁时,会先检查是不是已经上锁了:
    • 如果没有上锁, pthread_mutex_lockpthread_mutex_trylock都可以加锁成功,然后返回0
    • 如果已经上锁:
      • pthread_mutex_lock会阻塞住线程(挂起,不占用任何CPU),直到成功获取到了锁
      • pthread_mutex_trylock不会阻塞线程,而是立即返回EBUSY

如果发起 pthread_mutex_lock()调用的线程自身之前已然将目标互斥量锁定,对于互斥量
的默认类型而言,可能会产生两种后果—视具体实现而定:线程陷入死锁(deadlock),因
试图锁定已为自己所持有的互斥量而遭到阻塞;或者调用失败,返回 EDEADLK 错误。在 Linux上,默认情况下线程会发生死锁

函数 pthread_mutex_unlock()将解锁之前已遭调用线程锁定的互斥量。以下行为均属错误:

  • 对处于未锁定状态的互斥量进行解锁
  • 解锁由其他线程锁定的互斥量

如果有不止一个线程在等待获取由函数 pthread_mutex_unlock()解锁的互斥量,则无法判断究竟哪个线程将如愿以偿

执行锁超时

NAME
       pthread_mutex_timedlock - lock a mutex (ADVANCED REALTIME)

SYNOPSIS
       #include <pthread.h>
       #include <time.h>

       int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abs_timeout);

DESCRIPTION
		当前调用线程最多阻塞__abstime时间,如果超时还未回去到锁,就返回ETIMEDOUT。如果成功在给定
		时间了得到了锁,返回0

除了调用者可以指定一个附加参数 abstime(设置线程等待获取互斥量时休眠的时间限制)外,函数 pthread_mutex_timedlock()与 pthread_mutex_lock()没有差别。如果参数 abstime 指定的时间间隔期满,而调用线程又没有获得对互斥量的所有权,那么函数pthread_mutex_timedlock()返回 ETIMEDOUT 错误

互斥量的属性

typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;
/*
* 返回值: 成功0,失败返回错误编号
*/
int pthread_mutexattr_init (pthread_mutexattr_t *__attr)
/*
* 返回值: 成功0,失败返回错误编号
*/
int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr)

注意:

  • 同一线程不应对同一互斥量加锁两次。
  • 线程不应对不为自己所拥有的互斥量解锁(亦即,尚未锁定互斥量)。
  • 线程不应对一尚未锁定的互斥量做解锁动作。

准确地说,上述情况的结果将取决于互斥量类型(type)。SUSv3 定义了以下互斥量类型。

  • PTHREAD_MUTEX_NORMAL

    • 该类型互斥量不具有死锁检测功能。如果线程试图对已由自己锁定的互斥量进行加锁,则发生死锁。
    • 互斥量处于未锁定状态,或者已由其他线程锁定,对其解锁会导致不确定的结果。
    • (在 Linux 上,对这类互斥量的上述两种操作都会成功。)
  • PTHREAD_MUTEX_ERRORCHECK

    • 对此类互斥量的所有操作都会执行错误检查。所有上述 3 种情况都会导致相关 Pthreads 函数返回错误。
    • 这类互斥量运行起来比一般类型要慢,不过可将其作为调试工具,以发现程序在哪里违反了互斥量使用的基本原则
  • PTHREAD_MUTEX_RECURSIVE

    • 递归互斥量维护由一个锁计数器。当线程第 1 次取得互斥量时,会将锁计数器置 1。后续由同一线程执行的每次加锁操作会递增锁计数器的数值,而解锁操作则递减计数器计数。
    • 只有当锁计数器值降为0时,才会release该互斥锁(亦即可为其他线程所用)
    • 解锁时如目标互斥量处于未锁定状态,或是已由其他线程锁定,操作都会失败。

Linux 的线程实现针对以上各种类型的互斥量提供了非标准的静态初始值(例如,PTHREAD_ RECURSIVE_MUTEX_INITIALIZER_NP),以便对那些通过静态分配的互斥量进行初始化,而无需使用 pthread_mutex_init()函数。不过,为保证程序的可移植性,应该避免使用这些初始值。

除了上述类型,SUSv3 还定义了 PTHREAD_MUTEX_DEFAULT 类型。使用 PTHREAD_ MUTEX_INITIALIZER 初始化的互斥量,或是经调用参数 attr 为 NULL 的 pthread_mutex_init()函数所创建的互斥量,都属于此类型。至于该类型互斥量在本节开始处 3 个场景中的行为,规范有意未作定义,意在为互斥量的高效实现保留最大的灵活性。Linux 上,PTHREAD_MUTEX_DEFAULT类型互斥量的行为与 PTHREAD_MUTEX_NORMAL 类型相仿。

下面例子创建了一个错误检查属性的互斥量:

pthread_mutex_t mtx;
pthread_mutexattr_t mtxAttr;
int s, type;

s = pthread_mutexattr_init(&mtxAttr);
if(s != 0){
	exit(1);
}

s = pthread_mutexattr_settype(&mutex, PTHREAD_MUTEX_ERRORCHECK );
if(s != 0){
	exit(1);
}

s = pthread_mutex_lock(&lock); 
if(s != 0){
	exit(1);
}
s = pthread_mutex_unlock(&lock);  // 解锁 
if(s != 0){
	exit(1);
}

实践

初始化

  1. 静态初始化:static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;常用于全局锁。
#include <iostream>
#include <pthread.h>
#include <thread>


static pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;  


int a = 0;
void thread_tast(){
    for(int i = 0; i < 100000; i++){
        pthread_mutex_lock(&lock);   // 上锁(如果锁已经锁上,会阻塞直到拿到了锁)
        a++;
        pthread_mutex_unlock(&lock);  // 解锁 
    }
    printf("a = %d\n", a);  // 锁能够保证最终结果一定相同(但不保证过程),如果不加锁的话每次运行结果都不一样
}


int main() {
    std::thread t1(thread_tast);
    std::thread t2(thread_tast);
    t1.detach();
    t2.detach();

    getchar();


    return 0;
}

  1. 动态初始化:
#include <iostream>
#include <pthread.h>
#include <thread>

pthread_mutex_t  lock;
int a = 0;
void thread_tast(){
    for(int i = 0; i < 100000; i++){
        pthread_mutex_lock(&lock);
        a++;
        pthread_mutex_unlock(&lock);
    }
    printf("a = %d\n", a);
}


int main() {
    pthread_mutex_init(&lock, nullptr); // nullptr会创建一个默认的普通锁
    std::thread t1(thread_tast);
    std::thread t2(thread_tast);
    t1.detach();
    t2.detach();
    getchar();
    pthread_mutex_destroy(&lock);

    return 0;
}

锁在单线程多进程中

#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>


static void *thread_main(void * arg){
    pthread_mutex_t *__lock;
    int ret;
    char  ebuf[256];
    pid_t pid;

    __lock = (pthread_mutex_t*) calloc(1, sizeof(pthread_mutex_t));
    pthread_mutex_init(__lock, NULL);

    printf("current pid: %d, tid: %lu\r\n", getpid(), pthread_self());

    if( (ret = pthread_mutex_lock(__lock)) == 0){
        printf("0: main thread(%lu) lock ok\r\n", pthread_self());
    }else{
        snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
        printf("0: main thread(%lu) lock error(%d:%s)\r\n",pthread_self(), ret,ebuf);
    }


    switch ((pid = fork())) {
        case -1:
            printf("fork failed\r\n");
            free(__lock);
            exit (0);
        case 0:
            /* 先解锁 */
            if ((ret = pthread_mutex_unlock(__lock)) == 0)
                printf("1: child thread(%lu) unlock ok\r\n",pthread_self());
            else{
                snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
                printf("1: child thread(%lu) unlock error(%d:%s)\r\n",pthread_self(), ret,ebuf);
            }

            sleep(1);

              /* 再加锁 */
            if ((ret = pthread_mutex_lock(__lock)) == 0)
                printf("1: child thread(%lu) lock ok\r\n", pthread_self());
            else{
                snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
                printf("1: child thread(%lu) unlock error(%d:%s)\r\n",pthread_self(), ret,ebuf);
            }

            /* destroy锁--该函数会报错: 不能在没有lock锁的情况下destroy锁 */
            if ((ret = pthread_mutex_destroy(__lock)) == 0)
                printf("1: child thread(%lu) destroy ok\r\n", pthread_self());
            else{
                snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
                printf("1: child thread(%lu) destroy error(%d:%s)不能在没有lock锁的情况下destroy锁 \r\n",pthread_self(), ret,ebuf);
            }


            // 不能在destroy锁之后在lock 锁
           /* if ((ret = pthread_mutex_lock(__lock)) == 0)
                printf("1: child thread(%lu) lock ok\r\n", pthread_self());
            else{
                snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
                printf("1: child thread(%lu) unlock error(%d:%s)\r\n",pthread_self(), ret,ebuf);
            }*/


            free(__lock);
            printf("1: child thread(%lu) free lock, then exit\r\n",pthread_self());
            exit (0);
        default:
            printf("0: parent, child pid: %d, tid: %lu\r\n",
                   getpid(), pthread_self());



            {
                int  status;
                pid_t pp;
                pp = wait(&status);
                printf("wait's pid: %d, pid: %d, status: %d\r\n",pp, pid, status);
            }


            // 默认情况下,锁是共享的,当锁在某个线程中被销毁之后,就不能再次使用或者重复销毁了
            if ((ret = pthread_mutex_destroy(__lock)) == 0)
                printf("0: parent thread(%lu) destroy lock ok\r\n",
                       pthread_self());
            else{
                snprintf(ebuf, sizeof(ebuf), "%s", strerror(errno));
                printf("0: parent thread(%lu) destroy err(%d:%s) 默认情况下,锁是共享的,当锁在某个线程中被销毁之后,就不能再次使用或者重复销毁了\r\n",
                       pthread_self(), ret, ebuf);
            }

            free(__lock);
            break;
    }

    return NULL ;
}

static void test_thread(){
    pthread_attr_t attr;
    pthread_t      t1;

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 10240000);
    pthread_create(&t1, &attr, thread_main, NULL);
    pthread_join(t1, NULL);
    printf(">>> test_pthread_once: over now, enter any key to exit...\n");
    getchar();
}


int main(void)
{
    test_thread();
    return 0;
}


超时锁

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
int main(void)
{
    int err;
    struct timespec tout;
    struct tm *tmp;
    char buf[64];
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

    //pthread_mutex_lock(&lock);
    //printf("mutex is locked\n");

    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("current time is %s\n", buf);
    tout.tv_sec += 10;

    err = pthread_mutex_timedlock(&lock, &tout);
    if (err == 0){
        printf("mutex locked again!\n");
    }else{
        printf("can't lock mutex again: %s\n", strerror(err));
    }

    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("the time is now %s\n", buf);



    exit(0);
}

生产者消费者

  1. 生产者生产完所有的产品之后之后启动一个消费者
#include "unpipc.h"
#define	MAXNITEMS 		1000000   //最大条目数
#define	MAXNTHREADS			100   //最大线程数

int  nitems;
struct {  // 这些变量只能在拥有互斥锁的时候访问
    pthread_mutex_t  mutex;
    int buff[MAXNITEMS];
    int nput;  //buff数组下一次存放的元素下标
    int nval;  //下一次存放的值
} shared = {PTHREAD_MUTEX_INITIALIZER};  // PTHREAD_MUTEX_INITIALIZER 用在静态类型的互斥量中,而且应该在互斥量定义的时候就用 PTHREAD_MUTEX_INITIALIZER 进行初始化



void	*produce(void *), *consume(void *);

int main(int argc,char ** argv)
{
    int i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;  //用来保存线程ID数组
    if (argc != 3){
        err_quit("usage: prodcons1 <#items> <#threads>");
    }
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);
    printf("we create 最大放置空间 = %d, 工人个数 = %d, nput = %d, 线程车间大小 = %lu, count.size = %lu\n",
             nitems, nthreads, shared.nput, sizeof(tid_produce)/sizeof(tid_produce[0]), sizeof(count)/sizeof(count[0]));

   // Set_concurrency(nthreads);
    for(i = 0; i < nthreads; i++){ // 创建生产者线程
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);  // (线程ID, 默认属性, 线程启动后要执行的函数, 传给线程启动函数的参数)
    }

    int total = 0;
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL); // 等待线程1完成, 等待线程2完成, 等待线程3完成
        total = total + count[i];
        printf("count[%d] = %d\n", i, count[i]);
    }
    printf("total = %d\n",total);

    Pthread_create(&tid_consume, NULL, consume, NULL);
    Pthread_join(tid_consume, NULL);

}

void *produce(void *arg){
    while (1){
        pthread_mutex_lock(&shared.mutex);
        if (shared.nput >= nitems){ //
         //   printf("移动的下标超过最大空间数,没有空间可以存放了.  当前数组的下标 = %d, 最大空间 = %d, workID = %d\n", shared.nput, nitems, *((int *) arg));
            pthread_mutex_unlock(&shared.mutex);
            return (NULL); /* 退出当前进程 */
        }

        shared.buff[shared.nput] = shared.nval; // 将当前值存放到数组对应位置
        shared.nput++;      // 数组下标加1
        shared.nval++;      // 数组存放的值加1
        pthread_mutex_unlock(&shared.mutex);
        *((int *) arg) += 1;

    }
}

void *consume(void *arg)
{
    int		i;
    for (i = 0; i < nitems; i++) {
        if (shared.buff[i] != i)
            printf("buff[%d] = %d\n", i, shared.buff[i]);
    }
    return(NULL);
}

在这里插入图片描述
在这里插入图片描述

  1. 所有生产者线程启动之后立刻启动消费者线程
#include "unpipc.h"
#define	MAXNITEMS 		1000000   //最大条目数
#define	MAXNTHREADS			100   //最大线程数

int  nitems;
struct {  // 这些变量只能在拥有互斥锁的时候访问
    pthread_mutex_t  mutex;
    int buff[MAXNITEMS];
    int nput;  //buff数组下一次存放的元素下标
    int nval;  //下一次存放的值
} shared = {PTHREAD_MUTEX_INITIALIZER};  // PTHREAD_MUTEX_INITIALIZER 用在静态类型的互斥量中,而且应该在互斥量定义的时候就用 PTHREAD_MUTEX_INITIALIZER 进行初始化



void	*produce(void *), *consume(void *);

int main(int argc,char ** argv)
{
    int i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;  //用来保存线程ID数组
    if (argc != 3){
        err_quit("usage: prodcons1 <#items> <#threads>");
    }
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);
    printf("we create 最大放置空间 = %d, 工人个数 = %d, nput = %d, 线程车间大小 = %lu, count.size = %lu\n",
             nitems, nthreads, shared.nput, sizeof(tid_produce)/sizeof(tid_produce[0]), sizeof(count)/sizeof(count[0]));


    for(i = 0; i < nthreads; i++){ // 创建生产者线程
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);  // (线程ID, 默认属性, 线程启动后要执行的函数, 传给线程启动函数的参数)
    }

    Pthread_create(&tid_consume, NULL, consume, NULL);

    int total = 0;
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL); // 等待线程1完成, 等待线程2完成, 等待线程3完成
        total = total + count[i];
        printf("count[%d] = %d\n", i, count[i]);
    }
    printf("total = %d\n",total);
    Pthread_join(tid_consume, NULL);

}

void *produce(void *arg){
    while (1){
        pthread_mutex_lock(&shared.mutex);
        if (shared.nput >= nitems){ //
         //   printf("移动的下标超过最大空间数,没有空间可以存放了.  当前数组的下标 = %d, 最大空间 = %d, workID = %d\n", shared.nput, nitems, *((int *) arg));
            pthread_mutex_unlock(&shared.mutex);
            return (NULL); /* 退出当前进程 */
        }

        shared.buff[shared.nput] = shared.nval; // 将当前值存放到数组对应位置
        shared.nput++;      // 数组下标加1
        shared.nval++;      // 数组存放的值加1
        pthread_mutex_unlock(&shared.mutex);
        *((int *) arg) += 1;

    }
}

void  consume_wait(int i)
{
    for ( ; ; ) {
 
        Pthread_mutex_lock(&shared.mutex);
        if (i < shared.nput) {
            Pthread_mutex_unlock(&shared.mutex);
            return;			/* an item is ready */
        }
        Pthread_mutex_unlock(&shared.mutex);
    }
}

void *consume(void *arg)
{
    int		i;
    for (i = 0; i < nitems; i++) {
        consume_wait(i);  // 必须等待生产完了才能消费(通过判断当前的i是不是小于shared.nput--->shared_put是共享变量,我们在查看nput之前必须先获取到锁,因为某个生产者线程可能正处于更新该变量的过程中)
        if (shared.buff[i] != i)
            printf("buff[%d] = %d\n", i, shared.buff[i]);
    }
    return(NULL);
}

在这里插入图片描述
当消费某个变量时,这个变量还没有准备好,这里一次次轮循,每次给互斥量解锁又上锁来确保生产者准备好了,但是这非常浪费CPU。

解决方法:我们可以让当前消费者线程睡一会儿,让出相关资源(包括CPU和锁)

  1. sched_yield
#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

int		nitems;			/* read-only by producer and consumer */
struct {
    pthread_mutex_t	mutex;
    int	buff[MAXNITEMS];
    int	nput;
    int	nval;
} shared = { PTHREAD_MUTEX_INITIALIZER };

void	*produce(void *), *consume(void *);

/* include main */
int main(int argc, char **argv)
{
    int			i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;

    if (argc != 3)
        err_quit("usage: prodcons4 <#items> <#threads>");
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);

    Set_concurrency(nthreads + 1);
    /* 4create all producers and one consumer */
    for (i = 0; i < nthreads; i++) {
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    }
    Pthread_create(&tid_consume, NULL, consume, NULL);

    /* wait for all producers and the consumer */
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL);
        printf("count[%d] = %d\n", i, count[i]);
    }
    Pthread_join(tid_consume, NULL);

    exit(0);
}
/* end main */

void * produce(void *arg)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&shared.mutex);
        if (shared.nput >= nitems) {
            Pthread_mutex_unlock(&shared.mutex);
            return(NULL);		/* array is full, we're done */
        }
        shared.buff[shared.nput] = shared.nval;
        shared.nput++;
        shared.nval++;
        Pthread_mutex_unlock(&shared.mutex);
        *((int *) arg) += 1;
    }
}

/* include consume */
void consume_wait(int i)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&shared.mutex);
        if (i < shared.nput) {
            Pthread_mutex_unlock(&shared.mutex);
            return;			/* an item is ready */
        }
        Pthread_mutex_unlock(&shared.mutex);
        sched_yield();
    }
}

void *consume(void *arg)
{
    int		i;

    for (i = 0; i < nitems; i++) {
        consume_wait(i);
        if (shared.buff[i] != i)
            printf("buff[%d] = %d\n", i, shared.buff[i]);
    }
    return(NULL);
}
/* end consume */

sched_yield会让出当前线程的CPU占有权,然后把线程放到静态队列的尾端,然后一个新的CPU会占用CPU。那这个和sleep又什么区别呢?

  • sched_yield这个函数可以使用另一个级别等于或者高于当前线程的线程先运行,如果没有符合条件的线程,那么这个函数会立即返回然后继续执行当前线程的程序
  • sleep则是等待一定时间之后等待CPU的调度。

那么什么时候使用sched_yield呢?

  • 有策略的调用sched_yield能在资源竞争情况很严重的时候,通过给其他的线程或者进程运行机会的方式来提升程序的性能,让其他的各个线程或者进程都有机会运行。

条件变量

  1. 互斥量用于上锁,条件变量用于等待
  2. 每个条件变量总是有一个互斥锁与之相关联。

API

  1. 初始化一个条件变量
    条件变量是类型为pthread_cond_t的变量
#include <pthread.h>
/*
* 作用:初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定
* 返回值:函数成功返回0;任何其他返回值都表示错误
* 注意:不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。
* 
*/
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
  1. 阻塞等待条件变量
#include <pthread.h>
/*
 * 作用:等其线程阻塞等待条件变量上的pthread_mutex_t。被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
 * 返回值:函数成功返回0;任何其他返回值都表示错误
*/
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
  • 注意: pthread_cond_wait必须放在pthread_mutex_lockpthread_mutex_unlock之间,因为他要根据共享变量的状态来决定是否要等待,而为了不永远等待下去所以必须要在lock/unlock队中
  • pthread_cond_wait() 函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()pthread_cond_broadcast ,把该线程唤醒,使 pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex
  • Pthread_cond_wait在返回之前一定一定会获取到mutex并给它上锁
  1. 唤醒某一个条件变量的线程
/*
* 作用:发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态。
*      唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。
*      如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用     
* 返回值:函数成功返回0;任何其他返回值都表示错误
* 注意:
*/
int pthread_cond_signal(pthread_cond_t *cv);
  • 使用pthread_cond_signal不会有“惊群现象”产生,他最多只给一个线程发信号。( 但是 pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait)
  • pthread_cond_signal即可以放在pthread_mutex_lockpthread_mutex_unlock之间,也可以放在pthread_mutex_lockpthread_mutex_unlock之后,但是各有各缺点。
pthread_mutex_lock
xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
  • 缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的行为),所以一来一回会有性能的问题。

  • 我们假设系统中有线程1和线程2,他们都想获取mutex后处理共享数据,再释放mutex。请看这种序列:

    • 1)线程1获取mutex,在进行数据处理的时候,线程2也想获取mutex,但是此时被线程1所占用,线程2进入休眠,等待mutex被释放。
    • 2)线程1做完数据处理后,调用pthread_cond_signal()唤醒等待队列中某个线程,在本例中也就是线程2。线程1在调用pthread_mutex_unlock()前,因为系统调度的原因,线程2获取使用CPU的权利,那么它就想要开始处理数据,但是在开始处理之前,mutex必须被获取,很遗憾,线程1正在使用mutex,所以线程2被迫再次进入休眠。
    • 3)然后就是线程1执行pthread_mutex_unlock()后,线程2方能被再次唤醒。
      从这里看,使用的效率是比较低的,如果再多线程环境中,这种情况频繁发生的话,是一件比较痛苦的事情。
      但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
pthread_mutex_lock
xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
  • 优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
  • 缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。
  1. 阻塞直到指定时间
#include <pthread.h>
#include <time.h>
/*
* 作用:函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
* 返回值:函数成功返回0;任何其他返回值都表示错误(超时还没有收到信号返回的错误码是ETIMEDOUT)
*/
int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mp, const structtimespec * abstime);

有关于超时时间structtimespec,指的时一天中的某个时刻,比如

pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;  //秒
to.tv_nsec = 0;  // 那么
  1. 释放条件变量
#include <pthread.h>
/*
作用: 释放条件变量
返回值:函数成功返回0;任何其他返回值都表示错误
注意:条件变量占用的空间并未被释放。
*/
int pthread_cond_destroy(pthread_cond_t *cv);

参见
pthread_cond_wait
参见

实践

#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

/* globals shared by threads */
int		nitems;				/* read-only by producer and consumer */
int		buff[MAXNITEMS];
struct {
    pthread_mutex_t	mutex;
    pthread_cond_t	cond;
    int				nput;
    int				nval;
    int				nready;
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };

void	*produce(void *), *consume(void *);

/* include main */
int main(int argc, char **argv)
{
    int			i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;

    if (argc != 3)
        err_quit("usage: prodcons5 <#items> <#threads>");
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);

    Set_concurrency(nthreads + 1);
    for (i = 0; i < nthreads; i++) {
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    }
    Pthread_create(&tid_consume, NULL, consume, NULL);



    int total = 0;
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL);
        printf("count[%d] = %d\n", i, count[i]);
        total += count[i];
    }
    printf("total = %d\n", total);
    Pthread_join(tid_consume, NULL);

    exit(0);
}
/* end main */

void *produce(void *arg)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&nready.mutex); // 去获取锁,并上锁
        if (nready.nput >= nitems) { // 是不是还有活干
            Pthread_mutex_unlock(&nready.mutex);
            return(NULL);		/* array is full, we're done */
        }
        buff[nready.nput] = nready.nval; // 干活
        nready.nput++; // 干活
        nready.nval++; // 干活
        nready.nready++; // 干活
       // Pthread_cond_signal(&nready.cond); // 通知活干好了
        Pthread_mutex_unlock(&nready.mutex); // 解锁
        Pthread_cond_signal(&nready.cond); // 通知某一个”人“好了,与上面的注释任意一个就好
        *((int *) arg) += 1; // 积分加一
    }
}

/* include consume */
void *consume(void *arg)
{
    int		i;

    for (i = 0; i < nitems; i++) {
        Pthread_mutex_lock(&nready.mutex);
        while (nready.nready == 0){ // 如果还没有准备好
            Pthread_cond_wait(&nready.cond, &nready.mutex); //发现还没有准备好:先把锁还回去,然后退回队伍中
            // 先解除之前的pthread_mutex_lock锁定的mutex;第二 挂起,阻塞并在等待对列里休眠
        }  // 发现有个短信通知了”我“(并且送了一把锁过来),”我“上锁,然去查看是不是准备好
        nready.nready--; // 发现柜子里准备好了(因为一定是按照顺序准备的),拿走东西
        Pthread_mutex_unlock(&nready.mutex); // 消费完了,把锁还回去

        if (buff[i] != i) // 比较刚刚柜子里的东西与我是不是相配
            printf("buff[%d] = %d\n", i, buff[i]);
    }
    return(NULL);
}
/* end consume */

有可能生产了多个,然后才消费一个,但是一定会等待到有生产了才去消费。 问题是:如果有消费者正在消费,那么生产者就不能生产了(因为它们共用一把锁)。

分析:生产者需要改变放置物体的空间, 并且这个空间是共享的,但是这个空间有一个干了另一个就不要干了,因此这个生产者之间一定要共用一把锁以放置不安全。

问题是生产者和消费者需不需要共享这边锁呢?

生产者用过的空间就不需要访问了,从某种程度上,生产者和消费者是时间隔离空间共享的,有隔离就不构成危险共享,因此可以独用一把锁(如果共有一把锁那么生产者生产的时候消费者不能消费,消费者消费的时候生产者就不能生产了),就解决了这个问题(生产者生产的时候消费者尽管消费就好,只要这个产品已经生产好了,如果还没有生产好,就等待)

#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100


int		nitems;				/* read-only by producer and consumer */
int		buff[MAXNITEMS];
struct {
    pthread_mutex_t	mutex;
    int				nput;	/* next index to store */
    int				nval;	/* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };

struct {
    pthread_mutex_t	mutex;
    pthread_cond_t	cond;
    int				nready;	/* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };

void	*produce(void *), *consume(void *);

int main(int argc, char **argv)
{
    int			i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;

    if (argc != 3)
        err_quit("usage: prodcons6 <#items> <#threads>");
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);

    Set_concurrency(nthreads + 1);
    for (i = 0; i < nthreads; i++) {
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    }
    Pthread_create(&tid_consume, NULL, consume, NULL);

    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL);
        printf("count[%d] = %d\n", i, count[i]);
    }
    Pthread_join(tid_consume, NULL);

    exit(0);
}


void *produce(void *arg)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&put.mutex); // 生产上锁
        if (put.nput >= nitems) {
            Pthread_mutex_unlock(&put.mutex);
            return(NULL);		/* array is full, we're done */
        }
        buff[put.nput] = put.nval;
        put.nput++;
        put.nval++;
        Pthread_mutex_unlock(&put.mutex); // 生产解锁


        Pthread_mutex_lock(&nready.mutex); // 消费上锁
        nready.nready++;                    // 消费++
        Pthread_mutex_unlock(&nready.mutex); // 解锁
        Pthread_cond_signal(&nready.cond); // 通知人来消费

        *((int *) arg) += 1;
    }
}

void *consume(void *arg)
{
    int		i;

    for (i = 0; i < nitems; i++) {
        Pthread_mutex_lock(&nready.mutex); // 上锁
        while (nready.nready == 0) // 没有可以消费的
            Pthread_cond_wait(&nready.cond, &nready.mutex); // 解锁,然后等待唤醒----》唤醒了一定能获取到锁
        nready.nready--; // 消费
        Pthread_mutex_unlock(&nready.mutex); //消费之后解锁 

        if (buff[i] != i)  // 处理消费品
            printf("buff[%d] = %d\n", i, buff[i]);
    }
    return(NULL);
}

上面的代码还是有问题:没有必要生产一个就马上通知消费者来消费吧。可能cond_wait队列中没有等待消费的人呢?

/* include globals */
#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

/* globals shared by threads */
int		nitems;				/* read-only by producer and consumer */
int		buff[MAXNITEMS];
struct {
    pthread_mutex_t	mutex;
    int				nput;	/* next index to store */
    int				nval;	/* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };

struct {
    pthread_mutex_t	mutex;
    pthread_cond_t	cond;
    int				nready;	/* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
/* end globals */

int		nsignals;

void	*produce(void *), *consume(void *);

/* include main */
int
main(int argc, char **argv)
{
    int			i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;

    if (argc != 3)
        err_quit("usage: prodcons7 <#items> <#threads>");
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);

    Set_concurrency(nthreads + 1);
    /* 4create all producers and one consumer */
    for (i = 0; i < nthreads; i++) {
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    }
    Pthread_create(&tid_consume, NULL, consume, NULL);

    /* wait for all producers and the consumer */
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL);
        printf("count[%d] = %d\n", i, count[i]);
    }
    Pthread_join(tid_consume, NULL);
    printf("nsignals = %d\n", nsignals);

    exit(0);
}
/* end main */

void *produce(void *arg)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&put.mutex);
        if (put.nput >= nitems) {
            Pthread_mutex_unlock(&put.mutex);
            return(NULL);		/* array is full, we're done */
        }
        buff[put.nput] = put.nval;
        put.nput++;
        put.nval++;
        Pthread_mutex_unlock(&put.mutex);

        Pthread_mutex_lock(&nready.mutex);
        if (nready.nready == 0) { //发现没有消费品了,现在我又生产了一个
            Pthread_cond_signal(&nready.cond); // 通知人来消费
            nsignals++;
        }
        nready.nready++;
        Pthread_mutex_unlock(&nready.mutex);

        *((int *) arg) += 1;
    }
}

void *consume(void *arg)
{
    int		i;

    for (i = 0; i < nitems; i++) {
        Pthread_mutex_lock(&nready.mutex);
        while (nready.nready == 0)
            Pthread_cond_wait(&nready.cond, &nready.mutex);
        nready.nready--;
        Pthread_mutex_unlock(&nready.mutex);

        if (buff[i] != i)
            printf("buff[%d] = %d\n", i, buff[i]);
    }
    return(NULL);
}

问题:生产者线程Pthread_cond_signal发送信号之后,系统立即调度等待在其上的消费者线程,该消费者开始运行,但是立即停止,因为它不能获取到相应的互斥锁

/* include globals */
#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

/* globals shared by threads */
int		nitems;				/* read-only by producer and consumer */
int		buff[MAXNITEMS];
struct {
    pthread_mutex_t	mutex;
    int				nput;	/* next index to store */
    int				nval;	/* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };

struct {
    pthread_mutex_t	mutex;
    pthread_cond_t	cond;
    int				nready;	/* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
/* end globals */

int		nsignals;

void	*produce(void *), *consume(void *);

/* include main */
int
main(int argc, char **argv)
{
    int			i, nthreads, count[MAXNTHREADS];
    pthread_t	tid_produce[MAXNTHREADS], tid_consume;

    if (argc != 3)
        err_quit("usage: prodcons7 <#items> <#threads>");
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);

    Set_concurrency(nthreads + 1);
    /* 4create all producers and one consumer */
    for (i = 0; i < nthreads; i++) {
        count[i] = 0;
        Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    }
    Pthread_create(&tid_consume, NULL, consume, NULL);

    /* wait for all producers and the consumer */
    for (i = 0; i < nthreads; i++) {
        Pthread_join(tid_produce[i], NULL);
        printf("count[%d] = %d\n", i, count[i]);
    }
    Pthread_join(tid_consume, NULL);
    printf("nsignals = %d\n", nsignals);

    exit(0);
}
/* end main */

void *produce(void *arg)
{
    for ( ; ; ) {
        Pthread_mutex_lock(&put.mutex);
        if (put.nput >= nitems) {
            Pthread_mutex_unlock(&put.mutex);
            return(NULL);		/* array is full, we're done */
        }
        buff[put.nput] = put.nval;
        put.nput++;
        put.nval++;
        Pthread_mutex_unlock(&put.mutex);

        int dosignal;
        Pthread_mutex_lock(&nready.mutex);
        dosignal = (nready.nready == 0);
        nready.nready++;
        Pthread_mutex_unlock(&nready.mutex);

        if(dosignal){
            Pthread_cond_signal(&nready.cond);
            nsignals++;
        }

        *((int *) arg) += 1;
    }
}

void *consume(void *arg)
{
    int		i;

    for (i = 0; i < nitems; i++) {
        Pthread_mutex_lock(&nready.mutex);
        while (nready.nready == 0)
            Pthread_cond_wait(&nready.cond, &nready.mutex);
        nready.nready--;
        Pthread_mutex_unlock(&nready.mutex);

        if (buff[i] != i)
            printf("buff[%d] = %d\n", i, buff[i]);
    }
    return(NULL);
}

互斥锁与条件变量的属性

#include	"unpipc.h"

int main(int argc, char **argv)
{
    pthread_mutexattr_t	mattr; // 互斥锁的默认属性
    pthread_condattr_t	cattr; // 条件变量的默认属性


    for ( ; ; ) {
        Pthread_mutexattr_init(&mattr); // 初始化
        Pthread_mutexattr_destroy(&mattr); //销毁
        Pthread_condattr_init(&cattr);
        Pthread_condattr_destroy(&cattr);
    }

    exit(0);
}

如果需要在不同进程中共享互斥锁或者条件变量,而不是在单个进程中不同进程间共享怎么办?

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *mattr, int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);
int pthread_condattr_getpshared(const pthread_condattr_t *mattr, int *pshared);
int pthread_condattr_setpshared(pthread_condattr_t *mattr, int pshared);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值