image-20220714155743501

火狐浏览器开启的线程。PID进程号 LWP线程号

image-20220722163714455

image-20220714155957795

image-20220714160152562

image-20220714160305621

image-20220714162300308

/*
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
称之为子线程。
程序中默认只有一个进程,fork()函数调用,2进行
程序中默认只有一个线程,pthread_create()函数调用,2个线程。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

- 功能:创建一个子线程
- 参数:
- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
- attr : 设置线程的属性,一般使用默认值,NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
- arg : 给第三个参数使用,传参
- 返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: char * strerror(int errnum);
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
printf("child thread...\n");
printf("arg value: %d\n", *(int *)arg);
return NULL;
}

int main() {

pthread_t tid;

int num = 10;

// 创建一个子线程
int ret = pthread_create(&tid, NULL, callback, (void *)&num);

if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}

for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}

sleep(1);

return 0; // exit(0);
}

线程不是标准系统库 ,而是第三方

image-20220715000852721

因为主线程return 0 退出了,没执行子线程里的。只执行主线程。加了sleep就可以

image-20220722170427094


3.3 终止进程 pthread_exit

/*
#include <pthread.h>
void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
参数:
retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。

pthread_t pthread_self(void);
功能:获取当前的线程的线程ID

int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程ID是否相等
不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的
是使用结构体去实现的。
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
printf("child thread id : %ld\n", pthread_self());
return NULL; // pthread_exit(NULL);
}

int main() {

// 创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, callback, NULL);

if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}

// 主线程
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}

printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

// 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
pthread_exit(NULL);

printf("main thread exit\n");

return 0; // exit(0);
}

image-20220714234028445


3.4 连接已终止的现场 pthread_join

/*
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中使用
- 参数:
- thread:需要回收的子线程的ID
- retval: 接收子线程退出时的返回值
- 返回值:
0 : 成功
非0 : 失败,返回的错误号
*/

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

int value = 10; // 不能局部变量

void * callback(void * arg) {
printf("child thread id : %ld\n", pthread_self());
// sleep(3);
// return NULL;
// int value = 10; // 局部变量
pthread_exit((void *)&value); // return (void *)&value;
}

int main() {

// 创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, callback, NULL);

if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}

// 主线程
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}

printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

// 主线程调用pthread_join()回收子线程的资源
int * thread_retval;
ret = pthread_join(tid, (void **)&thread_retval); // 子线程不结束,join不执行

if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}

printf("exit data : %d\n", *thread_retval);

printf("回收子线程资源成功!\n");

// 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
pthread_exit(NULL);

return 0;
}

image-20220715093336175


3.5 线程的分离 pthread_detach

/*
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
1.不能多次分离,会产生不可预料的行为。
2.不能去连接一个已经分离的线程,会报错。
- 参数:需要分离的线程的ID
- 返回值:
成功:0
失败:返回错误号
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
printf("chid thread id : %ld\n", pthread_self());
return NULL;
}

int main() {

// 创建一个子线程
pthread_t tid;

int ret = pthread_create(&tid, NULL, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error1 : %s\n", errstr);
}

// 输出主线程和子线程的id
printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

// 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
ret = pthread_detach(tid);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error2 : %s\n", errstr);
}

// 设置分离后,对分离的子线程进行连接 pthread_join() 是错误的
// ret = pthread_join(tid, NULL);
// if(ret != 0) {
// char * errstr = strerror(ret);
// printf("error3 : %s\n", errstr);
// }

pthread_exit(NULL);

return 0;
}

image-20220715094417683

试试设置分离后,对分离的子线程进行join

image-20220715094516540

image-20220715094635605


3.6 线程取消 pthread_cancel

/*
#include <pthread.h>
int pthread_cancel(pthread_t thread);
- 功能:取消线程(让线程终止)
取消某个线程,可以终止某个线程的运行,
但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。
取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
printf("chid thread id : %ld\n", pthread_self());
for(int i = 0; i < 5; i++) {
printf("child : %d\n", i); //这个是一个取消点
}
return NULL;
}

int main() {
// 创建一个子线程
pthread_t tid;

int ret = pthread_create(&tid, NULL, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error1 : %s\n", errstr);
}

// 取消线程
pthread_cancel(tid);

for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}

// 输出主线程和子线程的id
printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

pthread_exit(NULL);
return 0;
}

image-20220715095844927

子线程并没有打印完5个,而是在printf时到达取消点,就终止。


3.7线程属性

image-20220715100202031

image-20220715100922322

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

int pthread_attr_destroy(pthread_attr_t *attr);
- 释放线程属性的资源

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
- 获取线程分离的状态属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 设置线程分离的状态属性
*/

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
printf("chid thread id : %ld\n", pthread_self());
return NULL;
}

int main() {

// 创建一个线程属性变量
pthread_attr_t attr;
// 初始化属性变量
pthread_attr_init(&attr);

// 设置属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

// 创建一个子线程
pthread_t tid;

int ret = pthread_create(&tid, &attr, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error1 : %s\n", errstr);
}

// 获取线程的栈的大小
size_t size;
pthread_attr_getstacksize(&attr, &size);
printf("thread stack size : %ld\n", size);

// 输出主线程和子线程的id
printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

// 释放线程属性资源
pthread_attr_destroy(&attr);

pthread_exit(NULL);

return 0;
}

image-20220715101755534


3.8 线程同步

设计 有3个窗口 一共100个窗口。

/*
使用多线程实现买票的案例。
有3个窗口,一共是100张票。
*/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 全局变量,所有的线程都共享这一份资源。
int tickets = 100; // 不能局部变量

void * sellticket(void * arg) {
// 卖票
while(tickets > 0) {
usleep(6000);
printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
tickets--;
}
return NULL;
}

int main() {

// 创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sellticket, NULL);
pthread_create(&tid2, NULL, sellticket, NULL);
pthread_create(&tid3, NULL, sellticket, NULL);

// 回收子线程的资源,阻塞
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);

// 设置线程分离。
// pthread_detach(tid1);
// pthread_detach(tid2);
// pthread_detach(tid3);

pthread_exit(NULL); // 退出主线程

return 0;
}

有问题

image-20220715102933418

image-20220715103248904

临界区指tickets


3.9 互斥锁、互斥量 mutex

image-20220715103532431

image-20220715114012298

image-20220715114054656

/*
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 初始化互斥量
- 参数 :
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
pthread_mutex_t *restrict mutex = xxx;
pthread_mutex_t * mutex1 = mutex; 不行

int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 释放互斥量的资源

int pthread_mutex_lock(pthread_mutex_t *mutex);
- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待

int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 尝试加锁,如果加锁失败,不会阻塞,会直接返回。

int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 解锁
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;

// 创建一个互斥量
pthread_mutex_t mutex;

void * sellticket(void * arg) {

// 卖票
while(1) {

// 加锁
pthread_mutex_lock(&mutex);

if(tickets > 0) {
usleep(6000);
printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
tickets--;
}else {
// 解锁
pthread_mutex_unlock(&mutex);
break;
}

// 解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main() {

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

// 创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sellticket, NULL);
pthread_create(&tid2, NULL, sellticket, NULL);
pthread_create(&tid3, NULL, sellticket, NULL);

// 回收子线程的资源,阻塞
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);

pthread_exit(NULL); // 退出主线程

// 释放互斥量资源
pthread_mutex_destroy(&mutex);

return 0;
}

但有可能还是一个线程,如果票数增加就正确。


3.10 死锁

image-20220715142644522

场景1:忘记释放锁,结果阻塞

image-20220715144054097

image-20220715144006208

场景2:重复加相同锁

image-20220715144247466

image-20220715144309560

场景3:多线程 多锁 抢占锁资源

image-20220715144806566

image-20220722212536063

image-20220715144553043

image-20220715145044194

使用资源有序分配法,线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。

所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。

img

谈一谈「互斥锁、自旋锁、读写锁、乐观锁、悲观锁」的选择和使用。

当已经有一个线程加锁后,其他线程加锁则就会失败,互斥锁和自旋锁对于加锁失败后的处理方式是不一样的:

互斥锁加锁失败后,线程会释放 CPU ,给其他线程;

自旋锁加锁失败后,线程会忙等待,直到它拿到锁;

互斥锁是一种「独占锁」,比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞。对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。

所以,互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。会有两次线程上下文切换的成本。所以,如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。

自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。

一般加锁的过程,包含两个步骤:

  • 第一步,查看锁的状态,如果锁是空闲的,则执行第二步;
  • 第二步,将锁设置为当前线程持有;

CAS 函数就把这两个步骤合并成一条硬件级指令,形成原子指令,这样就保证了这两个步骤是不可分割的,要么一次性执行完两个步骤,要么两个步骤都不执行。

使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会「忙等待」,直到它拿到锁。这里的「忙等待」可以用 while 循环等待实现,不过最好是使用 CPU 提供的 PAUSE 指令来实现「忙等待」,因为可以减少循环等待时的耗电量。需要注意,在单核 CPU 上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。

当加锁失败时,互斥锁用「线程切换」来应对,自旋锁则用「忙等待」来应对

写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。读写锁在读多写少的场景,能发挥出优势。

前面提到的互斥锁、自旋锁、读写锁,都是属于悲观锁。

悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁

那相反的,如果多线程同时修改共享资源的概率比较低,就可以采用乐观锁。

乐观锁做事比较乐观,它假定冲突的概率很低,它的工作方式是:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。可见,乐观锁的心态是,不管三七二十一,先改了资源再说。另外,你会发现乐观锁全程并没有加锁,所以它也叫无锁编程

实际上,我们常见的 SVN 和 Git 也是用了乐观锁的思想,先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。

乐观锁虽然去除了加锁解锁的操作,但是一旦发生冲突,重试的成本非常高,所以只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。


3.11 读写锁

image-20220715145139509

image-20220715181242740

/*
案例:8个线程操作同一个全局变量。
3个线程不定时写这个全局变量,5个线程不定时的读这个全局变量
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 创建一个共享数据
int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;

void * writeNum(void * arg) {

while(1) {
pthread_rwlock_wrlock(&rwlock);
num++;
printf("++write, tid : %ld, num : %d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}

return NULL;
}

void * readNum(void * arg) {

while(1) {
pthread_rwlock_rdlock(&rwlock);
printf("===read, tid : %ld, num : %d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}

return NULL;
}

int main() {

pthread_rwlock_init(&rwlock, NULL);

// 创建3个写线程,5个读线程
pthread_t wtids[3], rtids[5];
for(int i = 0; i < 3; i++) {
pthread_create(&wtids[i], NULL, writeNum, NULL);
}
for(int i = 0; i < 5; i++) {
pthread_create(&rtids[i], NULL, readNum, NULL);
}

// 设置线程分离
for(int i = 0; i < 3; i++) {
pthread_detach(wtids[i]);
}
for(int i = 0; i < 5; i++) {
pthread_detach(rtids[i]);
}

pthread_exit(NULL);

pthread_rwlock_destroy(&rwlock);

return 0;
}

用锁之前效果

image-20220715182857154

利用互斥锁试试,但是互斥锁,当有一个线程读的时候,其他线程不能读,效率太低。

image-20220715183025599

image-20220715183039703

image-20220715183110137

image-20220715183154178

image-20220715183338882

结果虽然正确。读写锁才能保证其他线程也能读,且效率更高。

image-20220715191430731


3.12 生产者消费者模型

image-20220715194155420

生产者 将东西 生产到容器中,消费者从容器中消费。当容器满了,生产者通知消费者取走东西,当容器为空,消费者通知生产者生产。

条件变量,信号量。

/*
生产者消费者模型(粗略的版本)
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 创建一个互斥量
pthread_mutex_t mutex;

struct Node{
int num;
struct Node *next;
};
// 头结点
struct Node * head = NULL;

void * producer(void * arg) {
// 不断的创建新的节点,添加到链表中
while(1) {
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
pthread_mutex_unlock(&mutex);
usleep(100);
}

return NULL;
}

void * customer(void * arg) {
while(1) {
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;

// 判断是否有数据
if(head != NULL) {
// 有数据
head = head->next;
printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
usleep(100);
} else {
// 没有数据
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}

int main() {

pthread_mutex_init(&mutex, NULL);

// 创建5个生产者线程,和5个消费者线程, 容器是列表
pthread_t ptids[5], ctids[5];

for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}

for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}

while(1) {
sleep(10);
}

pthread_mutex_destroy(&mutex);

pthread_exit(NULL);

return 0;
}

image-20220715220400658

出现问题,互斥量来确保同步,还需要 条件变量或信号量来解决生产者和消费者 同步问题。

以上代码是粗略代码造成的结果


3.13 条件变量

加上互斥锁之后结果:

image-20220715221736873

没问题,这么做但不好。需要生产者和消费者各自判断,最好需要通知方式。

image-20220715221857709

/*
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待,调用了该函数,线程会阻塞。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
- 唤醒所有的等待的线程
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;

struct Node{
int num;
struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {

// 不断的创建新的节点,添加到链表中
while(1) {
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());

// 只要生产了一个,就通知消费者消费
pthread_cond_signal(&cond);////

pthread_mutex_unlock(&mutex);
usleep(100);
}

return NULL;
}

void * customer(void * arg) {

while(1) {
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;
// 判断是否有数据
if(head != NULL) {
// 有数据
head = head->next;
printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
usleep(100);
} else {
// 没有数据,需要等待
// 当这个函数调用阻塞wait的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。从而生产者可以继续执行
pthread_cond_wait(&cond, &mutex); ////
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}

int main() {
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);

// 创建5个生产者线程,和5个消费者线程
pthread_t ptids[5], ctids[5];

for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}

for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}

while(1) {
sleep(10);
}

pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

pthread_exit(NULL);

return 0;
}

image-20220715233223049


3.14 信号量 semaphore

image-20220715233434744

/*
信号量的类型 sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 初始化信号量
- 参数:
- sem : 信号量变量的地址
- pshared : 0 用在线程间 ,非0 用在进程间
- value : 信号量中的值

int sem_destroy(sem_t *sem);
- 释放资源

int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

int sem_trywait(sem_t *sem);

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1

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

sem_t psem;
sem_t csem;
init(psem, 0, 8);
init(csem, 0, 0);

producer() {
sem_wait(&psem);
sem_post(&csem)
}

customer() {
sem_wait(&csem);
sem_post(&psem)
}

*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem;
sem_t csem;

struct Node{
int num;
struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {

// 不断的创建新的节点,添加到链表中
while(1) {
sem_wait(&psem);
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
pthread_mutex_unlock(&mutex);
sem_post(&csem);
}

return NULL;
}

void * customer(void * arg) {

while(1) {
sem_wait(&csem);
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;
head = head->next;
printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
sem_post(&psem);

}
return NULL;
}

int main() {

pthread_mutex_init(&mutex, NULL);
sem_init(&psem, 0, 8);
sem_init(&csem, 0, 0);

// 创建5个生产者线程,和5个消费者线程
pthread_t ptids[5], ctids[5];

for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}

for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}

while(1) {
sleep(10);
}

pthread_mutex_destroy(&mutex);

pthread_exit(NULL);

return 0;
}

image-20220716000312137