Linux多线程

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似于fork()。

线程创建

1
2
3
int pthread_create(pthread_t * restrict tidp,const pthread_attr_t * restrict attr,
void *(* start_rm)(void *),
void *restrict arg );

函数说明:tidp参数是一个指向线程标识符的指针,当线程创建成功后,用来返回创建的线程ID;attr参数用于指定线程的属性,NULL表示使用默认属性;start_rtn参数为一个函数指针,指向线程创建后要调用的函数,这个函数也称为线程函数;arg参数指向传递给线程函数的参数。

返回值:线程创建成功则返回0,发生错误时返回错误码。

因为pthread不是Linux系统的库,所以在进行编译时要加上-lpthread,例如:

1
gcc filename -lpthread

在代码中获得当前线程标识符的函数为:pthread_self()。

*线程退出void pthread_exit(void * rval_ptr);*

函数说明:rval_ptr参数是线程结束时的返回值,可由其他函数如pthread_join()来获取。

如果进程中任何一个线程调用exit()或_exit(),那么整个进程都会终止。线程的正常退出方式有线程从线程函数中返回、线程可以被另一个线程终止以及线程自己调用pthread_exit()函数。

线程等待int pthread_join(pthread_t tid, void ** rval_ptr);

在调用pthread_create()函数后,就会运行相关的线程函数了。pthread_join()是一个线程阻塞函数,调用后,则一直等待指定的线程结束才返回函数,被等待线程的资源就会被收回。

函数说明:阻塞调用函数,直到指定的线程终止。tid参数是等待退出的线程id;rval_ptr是用户定义的指针,用来存储被等待线程结束时的返回值(该参数不为NULL时)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void* thread_exe(void* parma) {
int* res = (int*)parma;
printf("child tid = %ld\n", pthread_self());
return (void*)20;
}

int main() {
pthread_t tid;
int parma = 20;
int* res_join;
pthread_create(&tid, NULL, thread_exe, &parma);
int res = pthread_join(tid, (void*)res_join);
printf("the child return value = %d\n", *res_join);
return 0;
}

执行结果:

1
2
3
china@ubuntu:~/Desktop/codefile/test/signal/socket$ ./test 
child tid = 140352593913600
the child return value = 20

线程清除

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行错误(比如访问非法地址)而退出,这种退出方式是不可预见的。

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利地释放自己所占用的资源,是一个必须考虑和解决的问题。

pthread_cleanup_push的调用点到pthread_cleanip_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行**pthread_cleanup_push()**所指定的清理函数。

1
void pthread_cleanup_push(void (* rtn)(void *), void * arg);

函数说明:将清除函数压入清除栈。rtn是清除函数,arg是清除函数的参数。

1
void pthread_cleanup_pop(int execute);

函数说明:将清除函数弹出清除栈。执行到pthread_cleanup_pop()时,参数execute决定是否在弹出清除函数的同时执行该函数,execute非0时,执行;execute为0时,不执行。

1
int pthread_cancel(pthread_t thread);

函数说明:取消线程,该函数在其他线程中调用,用来强行杀死指定的线程。

线程属性

之前线程创建函数pthread_create()函数的第二个参数都设置为了NULL,也就是说,都是采用的默认的线程属性。对于大多数的程序来说,使用默认属性就够了,但还是有必要来了解一下相关的属性。

属性结构为pthread_attr_t,属性值不能直接设置,必须使用相关的函数进行操作,初始化函数为pthread_attr_init(),这个函数必须在pthread_create()函数调用之前调用。

属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、默认1M的堆栈、与父进程同样级别的优先级。

线程绑定属性

关于绑定属性,涉及到另外一个概念:轻进程(Light Weight Process,LWP)。轻进程可以理解为内核进程,它位于用户层和内核层之间。系统对线程资源的分配和对线程的控制时通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认情况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定。绑定状况下,则顾名思义,即某个线程固定地绑在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

设置线程绑定状态的函数为pthread_attr_setscope,函数原型为:

1
int pthread_attr_setscope(pthread_attr_t * tattr, int scope);

函数说明:tattr参数为指向属性结构的指针,scope参数为绑定类型,通常有两个取值PTHREAD_SCOPE_SYSTEM(绑定)、PTHREAD_SCOPE_PROCESS(非绑定)。

返回值,pthread_sttr_setscope()成功完成后会返回0,其他任何返回值都表示出现了错误。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//
// test.c
// thread
//
// Created by echo on 2020/12/19.
// Copyright © 2020 echo. All rights reserved.
//

#include <errno.h> // error
#include <fcntl.h> // open
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // open
#include <sys/time.h> // gettimeofday sleep usleep
#include <sys/types.h> // open
#include <unistd.h> // close
//#include <linux/rtc.h> // rtc_commadn RTC_IRQP_SET / RTC_PIE_ON / RTC_PIE_OFF
//#include <sys/ioctl.h>
void* run(void* parma) {
char* p = (char*)parma;
printf("input string = %s\n", p);
int i = 0;
while (1) {
i++;
};
return (void*)0;
}

int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
int res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
if (res != 0) {
printf("绑定错误\n");
}
char* p = "child pthread";
int ret = pthread_create(&tid, &attr, run, &p);
printf("run done,ret = %d\n", ret);
pthread_join(tid, NULL);
return ret;
}

线程分离属性

线程的是否可结合状态决定线程以什么样的方式来终止自己。在任何一个时间点上,线程是可结合的(或非分离的,joinable)或者是分离的(detached)。

可结合属性:创建线程时,线程的默认属性是可结合的, 如果一个可结合线程结束运行但没有被pthread_join(),则它的状态类似于进程中的Zombie(僵死),即它的存储器资源(例如栈)是不释放的,所以创建线程者应该调用pthread_join()来等待线程运结束,并得到线程的退出码,回收其资源;

可分离属性:通过调用pthread_detach()函数该线程的可结合属性将被修改为可分离属性。一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

设置线程是否分离的函数为pthread_attr_setdatachstate(),其原型为:

1
int pthread_sttr_setdetachstate(pthread_sttr_t * tattr, int detachstate);

函数说明:tattr参数为指向属性结构的指针,detachstate参数为分离类型,通常有两个取值PTHREAD_CREATE_DETACHED(分离)、PTHREAD_CREATE_JOINABLE(非分离、结合)。

返回值,pthread_attr_setdatachstate()成功完成后会返回0,其他任何返回值都表示出现了错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//
// test.c
// thread
//
// Created by echo on 2020/12/19.
// Copyright © 2020 echo. All rights reserved.
//

#include <errno.h> // error
#include <fcntl.h> // open
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // open
#include <sys/time.h> // gettimeofday sleep usleep
#include <sys/types.h> // open
#include <unistd.h> // close
//#include <linux/rtc.h> // rtc_commadn RTC_IRQP_SET / RTC_PIE_ON / RTC_PIE_OFF
//#include <sys/ioctl.h>
void* run(void* parma) {
char** p = (char*)parma;
printf("input string = %s\n", *p);
int i = 0;
while (1) {
i++;
};
return (void*)0;
}

int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
// int res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
int res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
//通常有两个取值PTHREAD_CREATE_DETACHED(分离)、PTHREAD_CREATE_JOINABLE(非分离、结合)。
if (res != 0) {
printf("绑定错误\n");
}
char* p = "child pthread";
int ret = pthread_create(&tid, &attr, run, &p);
printf("run done,ret = %d\n", ret);
pthread_join(tid, NULL);
return ret;
}

注意,如果使用PTHREAD_CREATE_JOINABLE创建非分离线程(默认),则假设应用程序将等待线程完成。也就是说,在子线程终止后,必须要有一个线程用pthread_join()来等待它,否则就不会释放线程的资源,这将会导致内存泄漏。无论是创建的分离线程还是非分离线程,在所有线程都退出之前,进程都不会退出。

这与进程的wait()函数类似。

线程优先级属性

线程优先级存放在结构sched_param中,设置线程优先级的接口是pthread_attr_setschedparam(),它的完整定义是:

1
2
3
4
struct sched_param {
int sched_priority;
}
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//
// test.c
// thread
//
// Created by echo on 2020/12/19.
// Copyright © 2020 echo. All rights reserved.
//

#include <errno.h> // error
#include <fcntl.h> // open
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // open
#include <sys/time.h> // gettimeofday sleep usleep
#include <sys/types.h> // open
#include <unistd.h> // close
//#include <linux/rtc.h> // rtc_commadn RTC_IRQP_SET / RTC_PIE_ON / RTC_PIE_OFF
//#include <sys/ioctl.h>
void* run(void* parma) {
char** p = (char*)parma;
printf("input string = %s\n", *p);
int i = 0;
while (1) {
i++;
};
return (void*)0;
}

int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
struct sched_param parma;
int res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
// int res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
//通常有两个取值PTHREAD_CREATE_DETACHED(分离)、PTHREAD_CREATE_JOINABLE(非分离、结合)。
pthread_attr_setschedparam(&attr, &parma);
if (res != 0) {
printf("绑定错误\n");
}
char* p = "child pthread";
int ret = pthread_create(&tid, &attr, run, &p);
printf("run done,ret = %d\n", ret);
pthread_join(tid, NULL);
return ret;
}

线程的互斥

线程间的互斥是为了避免对共享资源或临界资源的同时使用,从而避免因此而产生的不可预料的后果。临界资源一次只能被一个线程使用。线程互斥关系是由于对共享资源的竞争而产生的间接制约。

互斥锁

假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。互斥锁用来保证一段时间内只有一个线程在执行一段代码,实现了对一个共享资源的访问进行排队等候。互斥锁是通过互斥锁变量来对访问共享资源排队访问。

互斥量

互斥量是pthread_mutex_t类型的变量。互斥量有两种状态:lock(上锁)、unlock(解锁)

当对一个互斥量加锁后,其他任何试图访问互斥量的线程都会被堵塞,直到当前线程释放互斥锁上的锁。如果释放互斥量上的锁后,有多个堵塞线程,这些线程只能按一定的顺序得到互斥量的访问权限,完成对共享资源的访问后,要对互斥量进行解锁,否则其他线程将一直处于阻塞状态。

操作函数

pthread_mutex_t是锁类型,用来定义互斥锁。

互斥锁的初始化

1
2
int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);
//const pthread_mutexattr_t * 通常给NULL

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

函数说明:mutex为互斥量,由pthread_mutex_init调用后填写默认值;attr属性通常默认为NULL。

上锁

1
2
int pthread_mutex_lock(pthread_mutex_t * mutex);
//函数说明:mutex为互斥量。

解锁

1
2
int pthread_mutex_unlock(pthread_mutex_t * mutex);
//函数说明:mutex为互斥量。

判断是否上锁

1
2
int pthread_mutex_trylock(pthread_mutex_t * mutex);
//返回值:0表示已上锁,非0表示未上锁。

销毁互斥锁

1
int pthread_mutex_destory(pthread_mutex_t * mutex);

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//
// test.c
// thread
//
// Created by echo on 2020/12/19.
// Copyright © 2020 echo. All rights reserved.
//

#include <errno.h> // error
#include <fcntl.h> // open
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // open
#include <sys/time.h> // gettimeofday sleep usleep
#include <sys/types.h> // open
#include <unistd.h> // close
//#include <linux/rtc.h> // rtc_commadn RTC_IRQP_SET / RTC_PIE_ON / RTC_PIE_OFF
//#include <sys/ioctl.h>

pthread_mutex_t mutex;

void* run(void* parma) {
pthread_mutex_lock(&mutex);
char* p = "?";
for (int i = 0; i < 5; i++) {
printf("input string = %s,my tid = %d\n", p, (int)pthread_self());
// sleep(2);
}
pthread_mutex_unlock(&mutex);
return (void*)0;
}
void* run2(void* parma) {
pthread_mutex_lock(&mutex);
char* p = "!";
for (int i = 0; i < 5; i++) {
printf("input string = %s,my tid = %d\n", p, (int)pthread_self());
// sleep(2);
}
pthread_mutex_unlock(&mutex);
return (void*)0;
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_t tid[2];

char* p = "im child thread";

pthread_create(&tid[0], NULL, run, NULL);
//这里不能传入参数???
pthread_create(&tid[1], NULL, run2, NULL);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
return 0;
}

读写锁(同步)

读写锁与互斥量类似,不过读写锁允许更改的并行性,也叫共享互斥锁。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态:读模式下加锁状态、写模式加锁状态、不加锁状态。

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁(允许多个线程读但只允许一个线程写)。

读写锁的特点

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

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

读写锁的规则

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

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

读写锁适合于对数据结构的读次数比写次数多得多的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <pthread.h>
// 初始化读写锁
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_trywrlock(pthread_rwlock_t *rwlock);
// 解锁
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
// 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 一个使用读写锁来实现 4 个线程读写一段数据是实例。
// 在此示例程序中,共创建了 4 个线程,
// 其中两个线程用来写入数据,两个线程用来读取数据
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
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;
}

自旋锁

自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁不同之处在于:当自旋锁尝试获取锁时以忙等待的形式不断地循环检查锁是否可用。在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

自旋锁和互斥锁的区别

从实现原理上来讲,互斥锁属于sleep-waiting类型的锁,而自旋锁属于busy-waiting类型的锁。也就是说:pthread_mutex_lock()**操作,如果没有锁成功的话就会调用system_wait()的系统调用并将当前线程加入该互斥锁的等待队列里;而pthread_spin_lock()则可以理解为,在一个while(1)循环中用内嵌的汇编代码实现的锁操作**(在linux内核中pthread_spin_lock()操作只需要两条CPU指令,unlock()解锁操作只用一条指令就可以完成)。

对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间

对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。

因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。

操作函数

pthread_spinlock_t是锁类型,用来定义自旋锁。

自旋锁的初始化

1
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

自旋锁的销毁

1
int pthread_spin_destroy(pthread_spinlock_t *lock);

上锁

1
int pthread_spin_lock(pthread_spinlock_t *lock);

判断是否上锁

1
int pthread_spin_trylock(pthread_spinlock_t *lock);

解锁

1
int pthread_spin_unlock(pthread_spinlock_t *lock);

信号量Semaphore

信号量同步

img

信号量用于互斥

img

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 信号量用于互斥实例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem; //信号量

void printer(char *str)
{
sem_wait(&sem);//减一,p操作
while(*str) // 输出字符串(如果不用互斥,此处可能会被其他线程入侵)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");

sem_post(&sem);//加一,v操作
}

void *thread_fun1(void *arg)
{
char *str1 = "hello";
printer(str1);
}

void *thread_fun2(void *arg)
{
char *str2 = "world";
printer(str2);
}

int main(void)
{
pthread_t tid1, tid2;

sem_init(&sem, 0, 1); //初始化信号量,初始值为 1

//创建 2 个线程
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);

//等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);

sem_destroy(&sem); //销毁信号量

return 0;
}

定义:

有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。

目的:

类似计数器,常用在多线程同步任务上,信号量可以在当前线程某个任务完成后,通知别的线程,再进行别的任务。

分类:

二值信号量:信号量的值只有0和1,这和互斥量很类似,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;

计数信号量:信号量的值在0到一个大于1的限制值之间,该计数表示可用的资源的个数。

信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁Mutex,即同时只能有一个任务可以访问信号量保护的共享资源

函数使用:

首先需要include <semaphore.h>这个库,没啥好说的,除非你自己实现内部函数。和互斥锁一样,也是四大金刚。

sem_init(简述:创建信号量)

1
2
3
4
5
int sem_init(sem_t *sem, int pshared, unsigned int value);
/*第一个参数:指向的信号对象
第二个参数:控制信号量的类型,如果其值为0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享
第三个参数:信号量sem的初始值
返回值:success为0,failure为-1*/

sem_post(简述:信号量的值加1)

1
2
3
int sem_post(sem_t *sem);
//第一个参数:信号量对象
//返回值:success为0,failure为-1

sem_wait(简述:信号量的值加-1)

1
2
3
int sem_wait(sem_t *sem);
//第一个参数:信号量对象
//返回值:success为0,failure为-1

sem_destroy(简述:销毁信号量)

1
2
3
int sem_destroy(sem_t *sem);
//第一个参数:信号量对象
//返回值:success为0,failure为-1

示例代码(生产者消费者实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//信号量---线程间通信
//“生产者消费者” 问题
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#define msleep(x) usleep(x*1000)
#define PRODUCT_SPEED 3 //生产速度
#define CONSUM_SPEED 1 //消费速度
#define INIT_NUM 3 //仓库原有产品数
#define TOTAL_NUM 10 //仓库容量

sem_t p_sem, c_sem, sh_sem;
int num=INIT_NUM;

void product(void) //生产产品
{
sleep(PRODUCT_SPEED);
}

int add_to_lib() //添加产品到仓库
{
num++;//仓库中的产品增加一个
msleep(500);
return num;
}

void consum() //消费
{
sleep(CONSUM_SPEED);
}

int sub_from_lib() //从仓库中取出产品
{
num--; //仓库中的产品数量减一
msleep(500);
return num;
}

void *productor(void *arg) //生产者线程
{
while(1)
{
sem_wait(&p_sem);//生产信号量减一
product();// 生产延时
sem_wait(&sh_sem);//这个信号量是用来互斥的
printf("push into! tatol_num=%d\n",add_to_lib());
sem_post(&sh_sem);
sem_post(&c_sem); //消费信号量加一
}
}

void *consumer(void *arg) //消费者线程
{
while(1)
{

sem_wait(&c_sem); //消费者信号量减一
sem_wait(&sh_sem);
printf("pop out! tatol_num=%d\n",sub_from_lib());
sem_post(&sh_sem);
sem_post(&p_sem);//生产者信号量加一
consum();//消费延时



}
}

int main()
{
pthread_t tid1,tid2;
sem_init(&p_sem,0,TOTAL_NUM-INIT_NUM);

sem_init(&c_sem,0,INIT_NUM);

sem_init(&sh_sem,0,1);

pthread_create(&tid1,NULL,productor,NULL);
pthread_create(&tid2,NULL,consumer,NULL);

pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}

青春版(生产者消费者)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <errno.h>  // error
#include <fcntl.h> // open
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // open
#include <sys/time.h> // gettimeofday sleep usleep
#include <sys/types.h> // open
#include <unistd.h> // close
#include <unistd.h>
//#include <linux/rtc.h> // rtc_commadn RTC_IRQP_SET / RTC_PIE_ON / RTC_PIE_OFF
//#include <sys/ioctl.h>

sem_t sem;
static int falg = 10;

void* consumer(void* parma);

void* product(void* parma) {
sem_wait(&sem);
// char** p = (char*)parma;
printf("IM product,flag remaining %d\n", falg);
sleep(1);
if (falg > 0) {
consumer(NULL);
}
return (void*)0;
}

void* consumer(void* parma) {
sem_post(&sem);
// char** p = (char*)parma;
printf("IM consumer,flag remaining %d\n", --falg);
sleep(1);
if (falg > 0) {
product(NULL);
}
return (void*)0;
}

int main() {
int resSemInit = sem_init(&sem, 0, 1);
if (resSemInit != 0) printf("信号量初始化失败!\n");
pthread_t tid[2];
// char* Product = "Productor";
// char* Consume = "Consumer";
// for (int i = 0; i < 10; i++) {
pthread_create(&tid[0], NULL, product, NULL); //(void*)Product);
pthread_create(&tid[1], NULL, consumer, NULL); //(void*)Consume);

pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
sem_destroy(&sem);
return 0;
}