想必各位读者在看了昨天的文章分享之后,大概对线程有了一个比较清楚的认识了,但是昨天讲的东西过于纯理论化,所以在昨天的基础上,今天我们就来进行实战演练,做到活学活用,废话不多说,直接开干吧。
一、从一个简单程序慢慢引进信号量:
1、一个小任务开始:用户从终端输入任意字符然后统计个数显示,输入end则结束。这个小任务对于大多数读者应该来说是小菜一碟的的,可以直接来看示例代码(今后写代码编程全程在vim里面写,虽然不舒服,还是要习惯的,hh):
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char buff[100]={0};
int main(void)
{
printf("please input your words:");
while(scanf("%s",buff))
{
if(strncmp(buff,"end",3)==0)
{
printf("over\n");
_exit(0);
}
printf("the input words is %d\n",strlen(buff));
}
return 0;
}
输出结果演示:
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
please input your words:f^[[A
the input words is 4
ffff
the input words is 4
ffff
the input words is 4
end
over
提示,上面字符串函数strncmp的用法稍微提一下:
#include <string.h>
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
DESCRIPTION
The strcmp() function compares the two strings s1 and s2. It returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be
greater than s2.
The strncmp() function is similar, except it compares only the first (at most) n bytes of s1 and s2.
RETURN VALUE
The strcmp() and strncmp() functions return an integer less than, equal to, or greater than zero if s1 (or the first n bytes thereof) is found, respectively, to be less than, to match, or be
greater than s2.
根据上面的源注释我们可以看到,strcmp的用法是(strncmp的用法和它一样,只不过是strncmp可以设置要比较字符大小的个数而已):
2、现在在上面那个程序中来创建线程操作,代码如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
char buff[100]={0};
void *fun(void *arg)
{
printf("hello pthread\n");
}
int main(void)
{
pthread_t pth=-1;
int ret=-1;
//create pthread
ret=pthread_create(&pth,NULL,fun,NULL);//最好是要有一个返回值验证是否线程创建成功
printf("please input your words:");
while(scanf("%s",buff))
{
if(strncmp(buff,"end",3)==0)
{
printf("over\n");
_exit(0);
}
printf("the input words is %d\n",strlen(buff));
}
return 0;
}
输出结果,这里可以显示到创建子线程成功,会打印出"hello pthread":
root@ubuntu-virtual-machine:/home/ubuntu# gcc 1.c -lpthread
1.c: In function ‘main’:
1.c:32:32: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t {aka long unsigned int}’ [-Wformat=]
printf("the input words is %d\n",strlen(buff));
~^ ~~~~~~~~~~~~
%ld
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
please input your words:hello pthread
jjjkj
the input words is 5
jjkl;j
the input words is 6
end
over
注意:
这里在Linux系统下编写线程程序时,使用gcc编译时,一定要在gcc编译语句后面加上"-lpthread"(如上面演示为例,这里加了一个-lpthread,表示要找到这个动态链接库来),不然的话会报下面这种错误,即使把创建线程函数prthread_create()的头文件#include <pthread.h>包含在程序当中,它任然会报下面这种错误:
tmp/cc1fzkqp.o:在函数‘main’中:
1.c:(.text+0x52):对‘pthread_create’未定义的引用
3、下面通过代码来引进线程同步信号量来实现子线程和主线程之间的通信控制。不过在这之前还是要稍微介绍一下什么是线程信号量,其实这个跟进程通信有点类似——Linux进程编程----syslog的使用和进程间通信的介绍(六):
a、那么什么是信号量呢?信号量用在多线程多任务同步的时候,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(可以理解日常打电话给女朋友,而你朋友却经常给你信号,不要烦她。其实说到这里,读者应该就能想到rtos里面的多任务操作了,这里我就不详细说了,hh)。
一个比较简单的单任务队列实现流程思路:
在这个过程中,任务分发器向任务队列中追加任务和工作线程取出任务的时候,都需要使用互斥锁对任务队列进行保护(不过这里还没设计到互斥锁的概念,你可以把互斥锁看成你平常开门和关门的动作执行就可以了,这样就很容易理解了。)。
这里在举一个来理解这个。比如说房产中介的工作模式就可以对这种工作模型有更加形象的理解。
b、信号量的类型:
信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态 (等下下面的代码示例就有这种写法)。
计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。
c、信号量函数介绍:
(1):信号量初始函数介绍:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -pthread.
DESCRIPTION
sem_init() initializes the unnamed semaphore at the address pointed to by sem. The value argument specifies the initial value for the semaphore.
The pshared argument indicates whether this semaphore is to be shared between the threads of a process, or between processes.
If pshared has the value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global variable, or a
variable allocated dynamically on the heap).
If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)). (Since a child created by
fork(2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post(3),
sem_wait(3), and so on.
Initializing a semaphore that has already been initialized results in undefined behavior.
RETURN VALUE
sem_init() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.
参数说明:
其中sem是要初始化的信号量,pshared表示此信号量是在进程间共享还是线程间共享,value是信号量的初始值。
(2):信号量销毁函数:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
Link with -pthread.
DESCRIPTION
sem_destroy() destroys the unnamed semaphore at the address pointed to by sem.
Only a semaphore that has been initialized by sem_init(3) should be destroyed using sem_destroy().
Destroying a semaphore that other processes or threads are currently blocked on (in sem_wait(3)) produces undefined behavior.
Using a semaphore that has been destroyed produces undefined results, until the semaphore has been reinitialized using sem_init(3).
RETURN VALUE
sem_destroy() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.
参数说明:
其中sem
是要销毁的信号量。只有用sem_init初始化的信号量才能用sem_destroy销毁。
(3):信号量等待函数:
#include <semaphore.h>
int sem_wait(sem_t *sem);
参数说明:
如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
(4):信号量唤醒函数(这个比较简单没啥好说的):
#include <semaphore.h>
int sem_post(sem_t *sem);
Link with -pthread.
DESCRIPTION
sem_post() increments (unlocks) the semaphore pointed to by sem. If the semaphore's value consequently becomes greater than zero, then another process or thread blocked in a sem_wait(3) call
will be woken up and proceed to lock the semaphore.
RETURN VALUE
sem_post() returns 0 on success; on error, the value of the semaphore is left unchanged, -1 is returned, and errno is set to indicate the error.
d、线程信号量代码示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
char buf[200] = {0};
sem_t sem;
unsigned int flag = 0;
// 子线程程序,作用是统计buf中的字符个数并打印
void *func(void *arg)
{
// 子线程首先应该有个循环
// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
// 长度,然后打印;完成后再次被阻塞
sem_wait(&sem);
//while (strncmp(buf, "end", 3) != 0)
while (flag == 0)
{
printf("本次输入了%d个字符\n", strlen(buf));
memset(buf, 0, sizeof(buf));
sem_wait(&sem);
}
pthread_exit(NULL);//终止子线程
}
int main(void)
{
int ret = -1;
pthread_t th = -1;
sem_init(&sem, 0, 0); //信号量初始化
ret = pthread_create(&th, NULL, func, NULL);
if (ret != 0)
{
printf("pthread_create error.\n");
exit(-1);
}
printf("please input the words:\n");
while (scanf("%s", buf))
{
// 去比较用户输入的是不是end,如果是则退出,如果不是则继续
if (!strncmp(buf, "end", 3))
{
printf("程序结束\n");
flag = 1;
sem_post(&sem); //这里发送"阻塞"信号
//exit(0);
break;
}
// 主线程在收到用户收入的字符串,并且确认不是end后
// 就去发信号激活子线程来计数。
// 子线程被阻塞,主线程可以激活,这就是线程的同步问题。
// 信号量就可以用来实现这个线程同步
sem_post(&sem); //这里发送激活信号
}
// 回收子线程
printf("等待回收子线程\n");
ret = pthread_join(th, NULL);
if (ret != 0)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("子线程回收成功\n");
sem_destroy(&sem);
return 0;
}
输出结果:
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
please input the words:
hh
本次输入了2个字符
hhh
本次输入了3个字符
end
程序结束
等待回收子线程
子线程回收成功
二、总结:
通过今天的线程信号量的学习,进步加深 了对多任务操作的理解。好了,今天的文章分享就到这里了,晚安!