linux线程同步

互斥锁

同步与互斥概述**

现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:

  • 都需要访问/使用同一种资源

  • 多个任务之间有依赖关系,某个任务的运行依赖于另一个任务

这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,同步和互斥就是用于解决这两个问题的。

互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个任务之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要按照某种次序来运行相应的线程(也是一种互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任务是无序的,而同步的任务之间则有顺序关系。

为什么需要互斥锁

在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。

下面我们用程序模拟一下这个过程,线程一需要打印“ hello ”,线程二需要打印“ world ”,不加任何处理的话,打印出来的内容会错乱:

测试程序:

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       模拟一下这个过程,线程一需要打印“ hello ”,线程二需要打印“ world ”,*       不加任何处理的话,打印出来的内容会错乱:*/void printer(char *str)
{while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");
}//线程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//线程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main()
{pthread_t tid1, tid2;//创建 2 个线程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待线程结束,回收其资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

运行结果如下:

实际上,打印机是有做处理的,我在打印着的时候别人是不允许打印的,只有等我打印结束后别人才允许打印。这个过程有点类似于,把打印机放在一个房间里,给这个房间安把锁,这个锁默认是打开的。当 A 需要打印时,他先过来检查这把锁有没有锁着,没有的话就进去,同时上锁在房间里打印。而在这时,刚好 B 也需要打印,B 同样先检查锁,发现锁是锁住的,他就在门外等着。而当 A 打印结束后,他会开锁出来,这时候 B 才进去上锁打印。

互斥锁Mutex介绍

而在线程里也有这么一把锁:互斥锁(mutex),也叫互斥量,互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )。

互斥锁的操作流程如下:

1)在访问共享资源后临界区域前,对互斥锁进行加锁。

2)在访问完成后释放互斥锁导上的锁。

3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。

互斥锁的数据类型是: pthread_mutex_t。

安装对应帮助手册:

sudo apt-get install manpages-posix-dev

pthread_mutex_init 函数

初始化互斥锁:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr);

功能:

        初始化一个互斥锁。

参数:

        mutex:互斥锁地址。类型是 pthread_mutex_t 。

        attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。

可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:

pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;

这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。

返回值:

成功:0,成功申请的锁默认是打开的。

失败:非 0 错误码

restrict,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。

pthread_mutex_destroy函数

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:

销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。

参数:

mutex:互斥锁地址。

返回值:

成功:0

失败:非 0 错误码

pthread_mutex_lock函数

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:

对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。

参数:

mutex:互斥锁地址。

返回值:

成功:0

失败:非 0 错误码

int pthread_mutex_trylock(pthread_mutex_t *mutex);

  • 调用该函数时,若互斥锁未加锁,则上锁,返回 0;
  • 若互斥锁已加锁,则函数直接返回失败,即 EBUSY。

pthread_mutex_unlock函数

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:

        对指定的互斥锁解锁。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非0错误码

测试程序

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       模拟一下这个过程,线程一需要打印“ hello ”,线程二需要打印“ world ”,*       加锁的代码,此时打印的结果不会错乱*/pthread_mutex_t mutex; //互斥锁//打印机
void printer(char *str)
{pthread_mutex_lock(&mutex); //上锁while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");pthread_mutex_unlock(&mutex); //解锁
}//线程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//线程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main(void)
{pthread_t tid1, tid2;pthread_mutex_init(&mutex, NULL); //初始化互斥锁//创建 2 个线程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待线程结束,回收其资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&mutex); //销毁互斥锁return 0;
}

结果:

死锁(DeadLock)

1)什么是死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

2)死锁引起的原因

竞争不可抢占资源引起死锁

也就是我们说的第一种情况,而这都在等待对方占有的不可抢占的资源。

竞争可消耗资源引起死锁

有p1,p2,p3三个进程,p1向p2发送消息并接受p3发送的消息,p2向p3发送消息并接受p1的消息,p3向p1发送消息并接受p2的消息,如果设置是先接到消息后发送消息,则所有的消息都不能发送,这就造成死锁。

进程推进顺序不当引起死锁

有进程p1,p2,都需要资源A,B,本来可以p1运行A --> p1运行B --> p2运行A --> p2运行B,但是顺序换了,p1运行A时p2运行B,容易发生第一种死锁。互相抢占资源。

3)死锁的必要条件

互斥条件

  • 某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完毕后释放资源。

请求和保持条件

  • 程序已经保持了至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,自己占用资源却保持不放。

不可抢占条件

  • 进程已获得的资源没有使用完,不能被抢占。

循环等待条件

  • 必然存在一个循环链。

4)处理死锁的思路

预防死锁

        破坏死锁的四个必要条件中的一个或多个来预防死锁。

避免死锁

        和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。

检测死锁

        运行时出现死锁,能及时发现死锁,把程序解脱出来

解除死锁

        发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

5)预防死锁的方法

破坏请求和保持条件

协议1:

所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

协议2:

允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。

破坏不可抢占条件

当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。

破坏循环等待条件

对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。

读写锁

读写锁概述

当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。

在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。

读写锁的特点如下:

1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。

2)如果有其它线程写数据,则其它线程都不允许读、写操作。

读写锁分为读锁和写锁,规则如下:

1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。

2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。

POSIX 定义的读写锁的数据类型是: pthread_rwlock_t。

pthread_rwlock_init函数

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

const pthread_rwlockattr_t *restrict attr);

功能:

用来初始化 rwlock 所指向的读写锁。

参数:

rwlock:指向要初始化的读写锁指针。

attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定的 attr 初始化读写锁。

可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:

pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;

这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。

返回值:

成功:0,读写锁的状态将成为已初始化和已解锁。

失败:非 0 错误码。

pthread_rwlock_destroy函数

#include <pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:

用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。

参数:

rwlock:读写锁指针。

返回值:

成功:0

失败:非 0 错误码

pthread_rwlock_rdlock函数

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

功能:

以阻塞方式在读写锁上获取读锁(读锁定)。

如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。

如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。

线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。

参数:

rwlock:读写锁指针。

返回值:

成功:0

失败:非 0 错误码

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

  • 用于尝试以非阻塞的方式来在读写锁上获取读锁。
  • 如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。

pthread_rwlock_wrlock函数

#include <pthread.h>

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

功能:

在读写锁上获取写锁(写锁定)。

如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。

如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。

参数:

rwlock:读写锁指针。

返回值:

成功:0

失败:非 0 错误码

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  • 用于尝试以非阻塞的方式来在读写锁上获取写锁。
  • 如果有任何的读者或写者持有该锁,则立即失败返回。

pthread_rwlock_unlock函数

#include <pthread.h>

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:

无论是读锁或写锁,都可以通过此函数解锁。

参数:

rwlock:读写锁指针。

返回值:

成功:0

失败:非 0 错误码

测试程序示例

下面是一个使用读写锁来实现 4 个线程读写一段数据是实例。

在此示例程序中,共创建了 4 个线程,其中两个线程用来写入数据,两个线程用来读取数据。当某个线程读操作时,其他线程允许读操作,却不允许写操作;当某个线程写操作时,其它线程都不允许读或写操作。

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       创建了 4 个线程,其中两个线程用来写入数据,两个线程用来读取数据。*       当某个线程读操作时,其他线程允许读操作,却不允许写操作;当某个线程写操作时,*       其它线程都不允许读或写操作。*/pthread_rwlock_t rwlock; //读写锁
int num = 1;//读操作,其他线程允许读操作,却不允许写操作
void *fun1(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num first===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(1);}
}//读操作,其他线程允许读操作,却不允许写操作
void *fun2(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num second===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(2);}
}//写操作,其它线程都不允许读或写操作
void *fun3(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread first\n");pthread_rwlock_unlock(&rwlock);sleep(2);}
}//写操作,其它线程都不允许读或写操作
void *fun4(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread second\n");pthread_rwlock_unlock(&rwlock);sleep(1);}
}int main()
{pthread_t ptd1, ptd2, ptd3, ptd4;pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁//创建线程pthread_create(&ptd1, NULL, fun1, NULL);pthread_create(&ptd2, NULL, fun2, NULL);pthread_create(&ptd3, NULL, fun3, NULL);pthread_create(&ptd4, NULL, fun4, NULL);//等待线程结束,回收其资源pthread_join(ptd1, NULL);pthread_join(ptd2, NULL);pthread_join(ptd3, NULL);pthread_join(ptd4, NULL);pthread_rwlock_destroy(&rwlock);//销毁读写锁return 0;
}

结果:

条件变量

条件变量概述

与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁

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

条件变量的两个动作:

  • 条件不满, 阻塞线程

  • 当条件满足, 通知阻塞的线程开始工作

条件变量的类型: pthread_cond_t。

pthread_cond_init函数

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

const pthread_condattr_t *restrict attr);

功能:

初始化一个条件变量

参数:

cond:指向要初始化的条件变量指针。

attr:条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

返回值:

成功:0

失败:非0错误号

pthread_cond_destroy函数

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

功能:

销毁一个条件变量

参数:

cond:指向要初始化的条件变量指针

返回值:

成功:0

失败:非0错误号

pthread_cond_wait函数

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex);

功能:

阻塞等待一个条件变量

a) 阻塞等待条件变量cond(参1)满足

b) 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);

a) b) 两步为一个原子操作。

c) 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

参数:

cond:指向要初始化的条件变量指针

mutex:互斥锁

返回值:

成功:0

失败:非0错误号

限时等待一个条件变量:

int pthread_cond_timedwait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex,

const struct *restrict abstime);

功能:

限时等待一个条件变量

参数:

cond:指向要初始化的条件变量指针

mutex:互斥锁

abstime:绝对时间

返回值:

   成功:0

   失败:非0错误号

abstime补充说明:

struct timespec {

        time_t tv_sec; /* seconds */ // 秒

        long   tv_nsec; /* nanosecondes*/ // 纳秒

}

time_t cur = time(NULL); //获取当前时间。

struct timespec t; //定义timespec 结构体变量t

t.tv_sec = cur + 1; // 定时1秒

pthread_cond_timedwait(&cond, &t);

pthread_cond_signal函数

唤醒至阻塞在条件变量上的线程

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

功能:

唤醒至少一个阻塞在条件变量上的线程

参数:

cond:指向要初始化的条件变量指针

返回值:

成功:0

失败:非0错误号

唤醒全部阻塞在条件变量上的线程:

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:

唤醒全部阻塞在条件变量上的线程

参数:

cond:指向要初始化的条件变量指针

返回值:

成功:0

失败:非0错误号

生产者消费者条件变量模型

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。

假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。

/*借助条件变量模拟 生产者-消费者 问题*/  
#include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <stdio.h>  /*链表作为公享数据,需被互斥量保护*/  
struct msg {  struct msg *next;  int num;  
};  struct msg *head;  /* 静态初始化 一个条件变量 和 一个互斥量*/  
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;  
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;  void *consumer(void *p)  
{  struct msg *mp;  for (;;) {  pthread_mutex_lock(&lock);  while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗  pthread_cond_wait(&has_product, &lock);   // 解锁,并阻塞等待  }  mp = head;        head = mp->next;                 //模拟消费掉一个产品  pthread_mutex_unlock(&lock);  printf("-Consume %lu---%d\n", pthread_self(), mp->num);  free(mp);  sleep(rand() % 5);  }  
}  void *producer(void *p)  
{  struct msg *mp;  for (;;) {  mp = malloc(sizeof(struct msg));  mp->num = rand() % 1000 + 1;        //模拟生产一个产品  printf("-Produce ---------------------%d\n", mp->num);  pthread_mutex_lock(&lock);  mp->next = head; //头插法 head = mp;  pthread_mutex_unlock(&lock);  pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程唤醒  sleep(rand() % 5);  }  
}  int main(int argc, char *argv[])  
{  pthread_t pid, cid;  srand(time(NULL));  pthread_create(&pid, NULL, producer, NULL);  pthread_create(&cid, NULL, consumer, NULL);  pthread_join(pid, NULL);  pthread_join(cid, NULL);  return 0;  
}  

结果:

一个生产者多个消费者模型

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  void err_thread(int ret, char *str)  
{  if (ret != 0) {  fprintf(stderr, "%s:%s\n", str, strerror(ret));  pthread_exit(NULL);  }  
}  struct msg {  int num;  struct msg *next;  
};  struct msg *head;  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定义/初始化一个互斥量  
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量  void *produser(void *arg)  
{  while (1) {  struct msg *mp = malloc(sizeof(struct msg));  mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`  printf("--produce %d\n", mp->num);  pthread_mutex_lock(&mutex);                         // 加锁 互斥量  mp->next = head;                                    // 写公共区域  head = mp;  pthread_mutex_unlock(&mutex);                       // 解锁 互斥量  pthread_cond_signal(&has_data);    // 唤醒阻塞在条件变量 has_data上的线程.  sleep(rand() % 3);  }  return NULL;  
}  void *consumer(void *arg)  
{  while (1) {  struct msg *mp;  pthread_mutex_lock(&mutex);                         // 加锁 互斥量  while (head == NULL) {  pthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁  }                                  // pthread_cond_wait 返回时, 重写加锁 mutex  mp = head;  head = mp->next;  pthread_mutex_unlock(&mutex);                       // 解锁 互斥量  printf("---------consumer id = %lu  :%d\n", pthread_self(), mp->num);  free(mp);  sleep(rand()%3);  }  return NULL;  
}  int main(int argc, char *argv[])  
{  int ret;  pthread_t pid, cid;  srand(time(NULL));  ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者  if (ret != 0)   err_thread(ret, "pthread_create produser error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  pthread_join(pid, NULL);  pthread_join(cid, NULL);  return 0;  
}  

结果:

条件变量的优缺点

相较于mutex而言,条件变量可以减少竞争。

如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。

有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

信号量

信号量概述

信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。

PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

信号量主要用于进程或线程间的同步和互斥这两种典型情况。

信号量数据类型为:sem_t。

信号量用于互斥:

信号量用于同步:

sem_init函数

初始化信号量:

#include <semaphore.h>

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

功能:

创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。

参数:

sem:信号量的地址。

pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。

value:信号量的初始值。

返回值:

成功:0

失败: - 1

sem_destroy函数

销毁信号量:

#include <semaphore.h>

int sem_destroy(sem_t *sem);

功能:

删除 sem 标识的信号量。

参数:

sem:信号量地址。

返回值:

成功:0

失败: - 1

信号量P操作(减1)

int sem_wait(sem_t *sem):

#include <semaphore.h>

int sem_wait(sem_t *sem);

功能:

将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,此函数会阻塞,直到信号量大于 0 时才进行减 1 操作。

参数:

sem:信号量的地址。

返回值:

成功:0

失败: - 1

int sem_trywait(sem_t *sem):

int sem_trywait(sem_t *sem);

  • 以非阻塞的方式来对信号量进行减 1 操作。
  • 若操作前,信号量的值等于 0,则对信号量的操作失败,函数立即返回。

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout):

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  • 限时尝试将信号量的值减 1
  • abs_timeout:绝对时间

abs_timeout补充说明:

struct timespec {

        time_t tv_sec; /* seconds */ // 秒

        long   tv_nsec; /* nanosecondes*/ // 纳秒

}

time_t cur = time(NULL); //获取当前时间。

struct timespec t; //定义timespec 结构体变量t

t.tv_sec = cur + 1; // 定时1秒

sem_timedwait(&cond, &t);

信号量V操作(加1)

#include <semaphore.h>

int sem_post(sem_t *sem);

功能:

将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。

参数:

sem:信号量的地址。

返回值:

成功:0

失败:-1

获取信号量的值

#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

功能:

获取 sem 标识的信号量的值,保存在 sval 中。

参数:

sem:信号量地址。

sval:保存信号量值的地址。

返回值:

成功:0

失败:-1

程序示例

/*信号量实现 生产者 消费者问题*/  #include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <stdio.h>  
#include <semaphore.h>  #define NUM 5                 int queue[NUM];                                     //全局数组实现环形队列  
sem_t blank_number, product_number;                 //空格子信号量, 产品信号量  void *producer(void *arg)  
{  int i = 0;  while (1) {  sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待  queue[i] = rand() % 1000 + 1;               //生产一个产品  printf("----Produce---%d\n", queue[i]);          sem_post(&product_number);                  //将产品数++  i = (i+1) % NUM;                            //借助下标实现环形  sleep(rand()%1);  }  
}  void *consumer(void *arg)  
{  int i = 0;  while (1) {  sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待  printf("-Consume---%d\n", queue[i]);  queue[i] = 0;                               //消费一个产品   sem_post(&blank_number);                    //消费掉以后,将空格子数++  i = (i+1) % NUM;  sleep(rand()%3);  }  
}  int main(int argc, char *argv[])  
{  pthread_t pid, cid;  sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享 -- 0  sem_init(&product_number, 0, 0);                //产品数为0  pthread_create(&pid, NULL, producer, NULL);  pthread_create(&cid, NULL, consumer, NULL);  pthread_join(pid, NULL);  pthread_join(cid, NULL);  sem_destroy(&blank_number);  sem_destroy(&product_number);  return 0;  
}  

结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87685.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87685.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 的IoC 和 AOP

第一部分&#xff1a;关于 IoC (控制反转) 1. 核心思想 (What & Why) 首先&#xff0c;我会先解释 IoC 的核心思想&#xff0c;而不是直接讲技术。 “IoC&#xff0c;即控制反转&#xff0c;它是一种重要的设计思想&#xff0c;而不是一个具体的技术。它的核心是将传统上…

[实战] Windows 文件读写函数 `ReadFile()` 和 `WriteFile()` 的阻塞与非阻塞操作详解(含完整C语言示例)

Windows 文件读写函数 ReadFile() 和 WriteFile() 的阻塞与非阻塞操作详解&#xff08;含完整C语言示例&#xff09; 在 Windows 平台进行文件或设备&#xff08;如串口、管道&#xff09;编程时&#xff0c;ReadFile() 和 WriteFile() 是最常用的两个 API 函数。它们既可以以…

Singularity 安装

Singularity 是什么? 核心功能:用于创建/运行容器(将应用+依赖打包的独立环境)。 与 Docker 的区别:专为 HPC(高性能计算)设计,无需后台守护进程,支持非 root 运行容器(但安装本身需 root 权限)。 适用于在具有 root 权限的计算机上从源代码安装 Singularity。…

辩证视角下 “辫子戏” 的文化反思与价值重构

前阵子播出的《人生若如初见》刻意美化晚清封建统治阶级&#xff0c;淡化甚至掩盖清政府闭关锁国、丧权辱国、残酷压迫民众等历史真相&#xff0c;将本应批判反思的腐朽统治包装成值得歌颂的对象&#xff1b;在历史叙事上&#xff0c;或通过虚构、篡改重要历史事件和人物形象&a…

MCP-server

&#x1f4a1; 说明&#xff1a;该模块是 MCP 服务器的 数据中继层&#xff0c;确保安全高效地从分布式来源获取模型及其上下文&#xff0c;适用于边缘计算和联邦学习场景。若要查看完整代码&#xff0c;建议直接访问 GitHub 链接

第3讲、LangChain性能优化:上下文缓存与流式响应实战指南

目录 概述上下文缓存优化流式响应优化复杂对话场景性能优化用户体验优化策略完整实现示例性能监控与调优总结 概述 在复杂对话场景中&#xff0c;大型语言模型面临着响应延迟、重复计算、上下文管理等挑战。本文将详细介绍如何通过LangChain的上下文缓存和流式响应功能来优化…

http中GET和POST、PUT之间的区别

在HTTP协议中&#xff0c;GET、POST和PUT是三种最常用的请求方法&#xff0c;它们的主要区别如下&#xff1a; 1. GET 用途&#xff1a;用于请求资源&#xff08;查询数据&#xff09;&#xff0c;不应修改服务器状态。 参数传递&#xff1a;通过URL的查询字符串&#xff08;…

埃夫特各种系列机器人运动学建模、轨迹规划和工作空间求解

要求&#xff1a; 1.理论分析 1.1 正向运动学&#xff1a;根据D-H法完成机器人的正向运动学&#xff08;数学建模后基于Matlab计算公式&#xff09;&#xff1b; 1.2 工作空间分析&#xff1a;根据正向运动学结果&#xff0c;运用 MATLAB进行工作空间分析&#xff0c;完成工…

VUE3 路由的跳转方法

Routerlink跳转方法 name属性对应了路由文件配置的name path属性对应了路由的路径 <RouterLink to"/login">点击跳转登陆</RouterLink> <RouterLink :to"{name:login}">点击跳转登陆</RouterLink> <RouterLink :to"{pat…

数据库中间件ShardingSphere5

一、高性能架构模式 数据库集群&#xff0c;第一种方式“读写分离”&#xff0c;第二种方式“数据库分片”。 1.1 读写分离架构 读写分离原理&#xff1a;将数据库读写操作分散到不同的节点上。 读写分离的基本实现&#xff1a; 主库负责处理事务性的增删改操作&#xff0c…

C++11 右值引用(Rvalue Reference)

在 C++11 中,右值引用(Rvalue Reference) 是一个革命性的语言特性,它为现代 C++ 的性能优化、资源管理以及语义清晰化奠定了基础。通过引入 T&& 语法,C++11 支持了 移动语义(Move Semantics) 和 完美转发(Perfect Forwarding),极大地提升了程序效率和代码表达…

skynet源码学习-skynet_main入口

skynet源码学习-skynet_main入口 核心功能与启动流程Shell脚本启动示例main函数参数处理其他相关联函数解析1. 配置加载器解析2. 环境变量设置3. 配置解析函数 核心配置项解析典型配置文件分析服务启动与运行核心服务启动流程完整启动时序图 核心功能与启动流程 Skynet 的启动…

前端图文混排页面一键导出PDF最佳实践 —— 以Vue3+html2pdf.js为例

前言 在现代管理系统中,数据的归档、分享和线下流转需求日益增长。如何将前端页面的图文内容高质量导出为PDF,成为许多企业和开发者关注的技术点。本文以实际项目为例,系统梳理前端导出PDF的完整实现思路与优化经验。 一、项目背景与需求分析 1.1 背景故事 在某管理系统的…

19|Whisper+ChatGPT:请AI代你听播客

今天&#xff0c;我们的课程开始进入一个新的主题了&#xff0c;那就是语音识别。过去几周我们介绍的ChatGPT虽然很强大&#xff0c;但是只能接受文本的输入。而在现实生活中&#xff0c;很多时候我们并不方便停下来打字。很多内容比如像播客也没有文字版&#xff0c;所以这个时…

linux常用设置

1&#xff0c;ubuntu设置ssh-agent进入shell时自动加载 一&#xff0c;添加自动加载脚本&#xff0c;vim /etc/profile.d/keychain.sh # /etc/profile.d/keychain.sh # 自动启动 ssh-agent 并加载多个私钥 export KEYCHAIN_HOME"/root/.keychain" # 多个key&#xf…

电子电气架构 --- 软件供应商如何进入OEM体系

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

破解数据可视化难题:带轴断裂的柱状图绘制全指南

引言&#xff1a;当数据跨度让图表失真时&#xff0c;轴断裂技术如何力挽狂澜&#xff1f; 在数据可视化的世界里&#xff0c;我们常常会遇到这样的困境&#xff1a;一组数据中既有 "巨无霸" 般的极端值&#xff0c;又有需要精细展示的小数据。比如在财务报表中&…

以太网基础①以太网相关通信接口

1. 今日摸鱼任务 需要学习使用ZYNQ的以太网传输SCPI指令 需要把PL PS两侧的都用起来&#xff08;加油鸭&#xff01;&#xff09; 呐呐呐 今天就先学一下基础知识呗 02_【逻辑教程】基于HDL的FPGA逻辑设计与验证教程V3.5.2.pdf 51 以太网相关通信接口详解 52 以太网&#xff…

FPGA基础 -- Verilog 共享任务(task)和函数(function)

Verilog 中共享任务&#xff08;task&#xff09;和函数&#xff08;function&#xff09; 的详细专业培训&#xff0c;适合具有一定 RTL 编程经验的工程师深入掌握。 一、任务&#xff08;task&#xff09;与函数&#xff08;function&#xff09;的基本区别 特性taskfunctio…

学习大模型---需要掌握的数学知识

1. 线性代数&#xff1a;乐高积木的世界 想象你有很多乐高积木块。线性代数就是研究怎么用这些积木块搭建东西&#xff0c;以及这些搭建好的东西有什么特性的学问。 向量&#xff1a; 就像一个有方向的箭头&#xff0c;或者一组排好队的数字。比如&#xff1a; 一个箭头&…