前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux线程编程之信号量

Linux线程编程之信号量

作者头像
用户6280468
发布2022-03-21 08:46:09
1.6K0
发布2022-03-21 08:46:09
举报
文章被收录于专栏:txp玩Linux

想必各位读者在看了昨天的文章分享之后,大概对线程有了一个比较清楚的认识了,但是昨天讲的东西过于纯理论化,所以在昨天的基础上,今天我们就来进行实战演练,做到活学活用,废话不多说,直接开干吧。

一、从一个简单程序慢慢引进信号量

1、一个小任务开始:用户从终端输入任意字符然后统计个数显示,输入end则结束。这个小任务对于大多数读者应该来说是小菜一碟的的,可以直接来看示例代码(今后写代码编程全程在vim里面写,虽然不舒服,还是要习惯的,hh):

代码语言:javascript
复制
#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;

}

输出结果演示:

代码语言:javascript
复制
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的用法稍微提一下:

代码语言:javascript
复制
 #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可以设置要比较字符大小的个数而已):

  • 若s1与s2相等,则返回0。
  • 若s1大于s2,则返回大于0的值。
  • 若s1 若小于s2,则返回小于0的值。
  • 注意一点:strcmp(const char *s1,const char * s2) 这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。
  • ANSI 标准规定,返回值为正数,负数,0 。而确切数值是依赖不同的C实现的。

2、现在在上面那个程序中来创建线程操作,代码如下:

代码语言:javascript
复制
  #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":

代码语言:javascript
复制
   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>包含在程序当中,它任然会报下面这种错误:

代码语言:javascript
复制
tmp/cc1fzkqp.o:在函数‘main’中:
1.c:(.text+0x52):对‘pthread_create’未定义的引用

3、下面通过代码来引进线程同步信号量来实现子线程和主线程之间的通信控制。不过在这之前还是要稍微介绍一下什么是线程信号量,其实这个跟进程通信有点类似——Linux进程编程----syslog的使用和进程间通信的介绍(六)

a、那么什么是信号量呢?信号量用在多线程多任务同步的时候,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(可以理解日常打电话给女朋友,而你朋友却经常给你信号,不要烦她。其实说到这里,读者应该就能想到rtos里面的多任务操作了,这里我就不详细说了,hh)。

一个比较简单的单任务队列实现流程思路:

  • 在没有工作负载时,所有的工作线程都阻塞在某个信号量上。
  • 当有任务需要处理时,任务分发器先把任务插入到任务队列。
  • 然后增加信号量,使得其中的一个工作线程被唤醒,被唤醒的工作线程会从任务队列中取出一个任务去执行

在这个过程中,任务分发器向任务队列中追加任务和工作线程取出任务的时候,都需要使用互斥锁对任务队列进行保护(不过这里还没设计到互斥锁的概念,你可以把互斥锁看成你平常开门和关门的动作执行就可以了,这样就很容易理解了。)。

这里在举一个来理解这个。比如说房产中介的工作模式就可以对这种工作模型有更加形象的理解。

  • 没有客户来的时候,员工们(工作线程)都各自坐在自己的工位上(阻塞在等待信号上)。
  • 有客户来找房子时(任务分发线程发布任务了),大家就会都来争抢这个客户,只有运气最好的一个员工能成功搭话这个客户(被信号量唤醒)。
  • 然后这个员工就会带这个客户出去看房(执行工作任务)。看房的过程通常不会很快,一出去可能就要半天时间(等待慢速的 I/O)。
  • 没抢到客户的员工回到自己的工位上继续等待

b、信号量的类型:

  • 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。
  • 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。
  • 记录型信号量(record semaphore):每个信号量s除一个整数value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态 (等下下面的代码示例就有这种写法)。

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

c、信号量函数介绍:

(1):信号量初始函数介绍:

代码语言:javascript
复制
#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):信号量销毁函数:

代码语言:javascript
复制
   #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):信号量等待函数:

代码语言:javascript
复制
 #include <semaphore.h>

   int sem_wait(sem_t *sem);

参数说明:

如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。

(4):信号量唤醒函数(这个比较简单没啥好说的):

代码语言:javascript
复制
 #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、线程信号量代码示例:

代码语言:javascript
复制
   #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;
}

输出结果:

代码语言:javascript
复制
  root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
  please input the words:
  hh
  本次输入了2个字符
  hhh
  本次输入了3个字符
  end
  程序结束
  等待回收子线程
  子线程回收成功

二、总结:

通过今天的线程信号量的学习,进步加深 了对多任务操作的理解。好了,今天的文章分享就到这里了,晚安!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档