2017-04-04 21:22:50 qq_29924041 阅读数 1721
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5047 人正在学习 去看看 朱有鹏

Linux线程浅析[线程的同步和互斥之线程信号量]

  1. 什么是线程信号量
  2. 线程信号量的相关函数
  3. 使用信号量来进行互斥和同步的问题

什么是线程信号量

 在之前有一篇博客讲了进程的信号量,singal函数,这个函数是用来捕获系统中某些信号的,而在线程中同样是有这样的一个信号两,这个信号量就是用来处理线程的同步互斥问题的.
 信号量的本质是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问信号量可以实现线程的同步和互斥,通过sem_post()sem_wait()函数对信号量 进行加减操作从而解决线程的同步和互斥
信号量数据类型: sem_t

线程信号量的相关函数

信号量数据类型:sem_t

#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned value);
int sem_destory(sem_t *sem);
返回:成功返回0,出错返回错误编号
参数:
    sem信号量指针
    pthread是否在进程间共享的标志,0为不共享,1为共享
    value初始的时候信号量值

信号量的加减操作

#include<semaphore.h>
int sem_post(sem_t *sem);
    功能:增加信号量的值
int sem_wait(sem_t *sem);
    功能:减少信号量的值
int sem_trywait(sem_t *sem);
    功能:sem_wait()的非阻塞版本
    返回:0成功,出错返回错误编码

 注意:
调用:sem_post()一次信号量作加1操作
调用sem_wait()一次信号量减1操作
当线程调用sem_wait后,若信号量的值小于0,则线程阻塞,只有其它线程在调用sem_post对信号量做加操作后并且其值大于或者等于0的时候,阻塞的线程才能继续运行;
一般调用原则:
如果当做互斥锁的时候,一般初始化的时候初始化为1,然后对其进行PV操作
如果需要进行同步的时候,一般初始化的时候信号量为0,然后对其进行PV操作

最简单的同步函数

/*
 * ===========================================================================
 *
 *       Filename:  pthread_sem_mutex.c
 *    Description:
 *    使用线程信号量来控制其执行的顺序,让执行顺序分别为one,two,three
 *        Version:  1.0
 *        Created:  2017年04月04日 20时41分25秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>

//定义两个全局变量,sem_one,sem_two
sem_t sem_one,sem_two;

/* *
 *线程1执行的函数,由于其第一个执行,所以是不需要进行等待的,但是其需要唤醒别的
 所以在执行结束后需要post去将计数器加1
 * */
void *one_th(void* argv){
  printf("one:%lx runing\n",pthread_self());
  sem_post(&sem_one);
  return (void*)0;
}

/* *
 *线程2执行的函数
 * */
void *two_th(void* argv){
  sem_wait(&sem_one);

  printf("two:%lx runing\n",pthread_self());
  sem_post(&sem_two);
  return (void*)0;
}

/* *
 *线程3执行的函数
 * */
void *three_th(void* argv){
  sem_wait(&sem_two);
  printf("three:%lx runing\n",pthread_self());
  return (void*)0;
}


int main(int argc,char * argv[]){
  pthread_t thread_one,thread_two,thread_three;

  //初始化函数,因为是同步,所以将其初始化的时候设置为0
  sem_init(&sem_one,0,0);
  sem_init(&sem_two,0,0);
  int err = 0 ;

  if((err = pthread_create(&thread_one,NULL,one_th,(void*)0)) != 0){
      perror("one thread create error");
  }

  if((err = pthread_create(&thread_two,NULL,two_th,(void*)0)) != 0){
      perror("one thread create error");
  }

  if((err = pthread_create(&thread_three,NULL,three_th,(void*)0)) != 0){
      perror("one thread create error");
  }


  pthread_join(thread_one,NULL);
  pthread_join(thread_two,NULL);
  pthread_join(thread_three,NULL);
  //信号量的销毁函数
  sem_destroy(&sem_one);
  sem_destroy(&sem_two);

  return 0;
}

输出的结果为:这里写图片描述

如何使用信号量来实现线程的互斥呢???
wait的时候是信号减1,post是加1,如果要互斥的话,那么必定只能有一个线程能去操纵共享资源.所以第一个进去的必定是执行的,有两种方法,1:将信号的初始值设置为1,或者进去先post,但是如果先post的话,也就意味着每个线程都可以去执行.所以只能初始化的时候设置为1,然后在执行的时候,将信号量做-1操作

对之前的银行账户相关的案例进行一种改写:

/*
 * ===========================================================================
 *
 *       Filename:  account.h
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年03月28日 22时04分18秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#ifndef __ACCOUNT_H_
#define __ACCOUNT_H_
//声明银行账户的结构体
//引用pthread头文件.方便后面加锁
#include<pthread.h>
#include<semaphore.h>
  typedef struct{
    int code;
    double balance;
    //将锁声明在结构体中,有助于每一个这样的结构体都有一把锁,那么在操作的时候,有利于优化并发效果
    //pthread_mutex_t pthreadmutex;
    //pthread_rwlock_t pthreadrwlock;
    //
    //初始化信号量
    sem_t sem_lock;
  }Account;

//创建银行账户
extern Account* create_account(int code,double balance);

//销毁一个账户
extern void destory_account(Account* account);

//存款
extern double withdraw(Account* account,double amt);

//取款
extern double deposit(Account* a,double amt);

//查看账户余额
extern double get_balance(Account* account);

#endif
/*
 * ===========================================================================
 *
 *       Filename:  account.c
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年03月30日 21时16分28秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include"account.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
#include<assert.h>
#include<pthread.h>
#include<semaphore.h>
//static pthread_mutexattr_t attr;
/* 
 *创建一个账户
 *返回这个账户的指针
 *
 * */
extern Account* create_account(int code,double balance){
    Account* account = (Account*)malloc(sizeof(Account));
    assert(account !=NULL);
    account->code = code;
    account->balance = balance;
    //初始化锁的过程
    //pthread_mutexattr_init(&attr);
    //pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL);
    //pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
    //pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ERRORCHECK);
    //pthread_mutex_init(&account->pthreadmutex,NULL);
    //pthread_mutex_init(&account->pthreadmutex,&attr);

    //初始化读写锁
    // pthread_rwlock_init(&account->pthreadrwlock,NULL);

    // 初始化信号量
    sem_init(&account->sem_lock,0,1);

    return account;
}
/* *
 *销毁一个账户
 * */
extern void destory_account(Account* account){
   assert(account!=NULL);
   free(account);
   //销毁锁的过程
   //pthread_mutexattr_destroy(&attr);
   //pthread_mutex_destroy(&account->pthreadmutex);
   //
   //销毁读写锁
   //pthread_rwlock_destroy(&account->pthreadrwlock);
   sem_destroy(&account->sem_lock);
}

/* *
 *取款,返回的是账户的余额
 *
 * */
extern double withdraw(Account * account,double amt){
  assert(account != NULL);
  //上锁的过程
  //pthread_mutex_lock(&account->pthreadmutex);
  //上读写锁(读锁)
  //pthread_rwlock_wrlock(&account->pthreadrwlock);

  sem_wait(&account->sem_lock);

  if(amt < 0 || amt > account->balance){
    //解锁的过程
    //pthread_mutex_unlock(&account->pthreadmutex);
    //解读写锁
    //pthread_rwlock_unlock(&account->pthreadrwlock);
    //信号量post
    sem_post(&account->sem_lock);
    return 0.0;
  }
  int balance = account -> balance;
  sleep(1);
  int result = balance - amt;
  account ->balance = result;
  //解锁的过程
  //pthread_mutex_unlock(&account->pthreadmutex);
  //
  //解读写锁
  //pthread_rwlock_unlock(&account->pthreadrwlock);
  //信号量post
  sem_post(&account->sem_lock);

  return amt;
}
/* *
 *存款,返回的也是账户的余额
 *
 * */
extern double deposit(Account* account,double amt){
   assert(account != NULL);
   //上锁的过程
   //pthread_mutex_lock(&account->pthreadmutex);
  //上读写锁(读锁)
  //pthread_rwlock_wrlock(&account->pthreadrwlock);
  //信号量wait
  sem_wait(&account->sem_lock);
   if(amt < 0){
     //解锁的过程
    //pthread_mutex_unlock(&account->pthreadmutex);
    //解读写锁
    //pthread_rwlock_unlock(&account->pthreadrwlock);
    //信号量post
    sem_post(&account->sem_lock);
      return 0.0;
   }
   int balance = account ->balance;
   sleep(2);
   int result = balance + amt;
   account->balance = result;
   //解锁的过程
   //pthread_mutex_unlock(&account->pthreadmutex);
    //解读写锁
    //pthread_rwlock_unlock(&account->pthreadrwlock);

   sem_post(&account->sem_lock);

   return result;
}

/* *
 *获取的就是这个账户的余额
 *
 * */
extern double get_balance(Account* account){
  assert(account !=NULL);
  //上读写锁(读锁)
  // pthread_rwlock_rdlock(&account->pthreadrwlock);

  sem_wait(&account->sem_lock);
  int result = account->balance;
  sem_post(&account->sem_lock);

  //解决读写锁
  //pthread_rwlock_unlock(&account->pthreadrwlock);
  return result;
}

这些就是简单的关于线程信号量相关的使用过程,注意在作为同步的时候使用的话,那么将初始计数器设置为0,而作为互斥锁来使用的话,那么将初始计数器的数值设置为1.

欢迎持续访问博客

2018-09-30 14:06:36 TSZ0000 阅读数 88
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5047 人正在学习 去看看 朱有鹏

Linux-线程同步信号量

    信号量用于多线程多任务同步,一个线程完成了某一个动作后就通过信号量告诉其他线程再进行某些动作,其他线程在等待某一个线程时会阻塞。

    init
        sem_t sem; 
        sem_init(&sem,0,0); 函数原型如下:
        int sem_init(sem_t *sem, int pshared, unsigned int value);
            pshared:0:shared between the threads of process.
                           !0:shared between processes.
            value: initial value for the semaphore.
    wait
        sem_wait(&sem);    
        P操作,if(value>0);value--;执行代码。if(value<=0);阻塞并等待post把value+1。
    post
        sem_post(&sem);
        V操作,value++;通知wait进行value的原子操作,V操作不阻塞继续执行。
    trywait
        sem_trywait(&sem);
        非阻塞版,如果信号量计数大于0,则信号量立即减1并返回0,否则立即返回-1。
    getvalue
        sem_getvalue(&sem, &sval)
        读取sem中信号量计数,存于sval中,并返回0。

/*gcc sem.c -lpthread*/
//用户从终端输入任意字符然后统计个数显示,输入end则结束
//使用多线程实现:主线程获取用户输入并判断是否退出,子线程计数
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>

char buf[100]={0}; 
int flag = 0;
sem_t sem;

// 子线程程序,作用是统计buf中的字符个数并打印
void *func(void*arg)
{
	// 子线程首先应该有个循环
	// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
	// 长度,然后打印;完成后再次被阻塞
	sem_wait(&sem);

	while(flag==0)
	{
		printf("长度为:%ld.\n",strlen(buf));
		memset(buf, 0, sizeof(buf));
		sem_wait(&sem);
	}
    pthread_exit(NULL);
}


int main(void)
{
	int ret=-1;
	pthread_t th;

	sem_init(&sem,0,0);

	ret=pthread_create(&th,NULL,func,NULL);
	if (ret != 0)
	{
		printf("pthread_create error.\n");
		return -1;
	}                    

	printf("输入一个字符串,以回车结束.\n");
	while(scanf("%s",buf))
	{
		// 去比较用户输入的是不是end,如果是则退出,如果不是则继续 
		if(!strncmp(buf,"end",3))
		{
			printf("输入的字符串为:%s\n",buf);
			flag=1;
			sem_post(&sem);
			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;
 }


//intsem_wait(sem_t *sem); 等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。
//如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
//intsem_post(sem_t *sem); 释放信号量,让信号量的值加1。相当于V操作。

 

2018-02-23 17:20:50 caoshunxin01 阅读数 35
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5047 人正在学习 去看看 朱有鹏

http://baike.baidu.com/link?url=3NI557PjXqE_AAL20W8Fzpq6gaHuPgWwMdnyaRWx3YdlgVJgMu8gYRC4h3tE6tuKiCq3FMn8LqSqdmWG0bP40q信号量和互斥锁(mutex)的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。

不多做解释,要使用信号量同步,需要包含头文件semaphore.h。

主要用到的函数:

  • int sem_init(sem_t *sem, int pshared, unsigned int value);,其中sem是要初始化的信号量,pshared表示此信号量是在进程间共享还是线程间共享,value是信号量的初始值。
  • int sem_destroy(sem_t *sem);,其中sem是要销毁的信号量。只有用sem_init初始化的信号量才能用sem_destroy销毁。
  • int sem_wait(sem_t *sem);等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
  • int sem_post(sem_t *sem); 释放信号量,让信号量的值加1。相当于V操作。

下列的代码演示了如何用信号量同步,模拟一个窗口服务系统。




#include  pthread.h
#include  semaphore.h
#include  unistd.h
#include  stdio.h
#include  stdlib.h





sem_t sem;


void * get_service(void *thread_id)
{
    
    int customer_id = *((int *)thread_id);

    if(sem_wait(&sem) == 0) {
        usleep(100);                
        printf("customer %d receive service ...\n", customer_id);
        sem_post(&sem);
    }
}

#define CUSTOMER_NUM 10

int main(int argc, char *argv[])
{
    
    
    
    sem_init(&sem, 0, 2);

    
    pthread_t customers[CUSTOMER_NUM];

    int i, ret;
    
    for(i = 0; i < CUSTOMER_NUM; i++){
        int customer_id = i;
        ret = pthread_create(&customers[i], NULL, get_service, &customer_id);
        if(ret != 0){
            perror("pthread_create");
            exit(1);
        }
        else {
            printf("Customer %d arrived.\n", i);
        }
        usleep(10);
    }

    
    
    int j;
    for(j = 0; j < CUSTOMER_NUM; j++) {
        pthread_join(customers[j], NULL);
    }

    
    sem_destroy(&sem);
    return 0;
}

编译:gcc main.c -lpthread

运行结果(注意,每次运行都不相同):


Customer 0 arrived.
Customer 1 arrived.
customer 0 receive service ...
Customer 2 arrived.
customer 1 receive service ...
Customer 3 arrived.
customer 2 receive service ...
Customer 4 arrived.
customer 3 receive service ...
Customer 5 arrived.
customer 4 receive service ...
Customer 6 arrived.
customer 5 receive service ...
Customer 7 arrived.
customer 6 receive service ...
Customer 8 arrived.
customer 7 receive service ...
Customer 9 arrived.
customer 8 receive service ...
customer 9 receive service ...
2012-09-15 20:54:10 hubi0952 阅读数 2014
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5047 人正在学习 去看看 朱有鹏

 sem_wait函数也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,这信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就 会地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。

sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。信号量的值永远会正确地加一个“2”--因为有两个线程试图改变它。

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

int myglobal;
sem_t sem;

void * thread_function(void *arg)
{
	int i,j;
	for (i = 0; i < 10; i += 1)
	{
		sem_wait(&sem);
		j = myglobal;
		j = j+1;
		printf(".");
		fflush(stdout);
		sleep(1);
		myglobal = j;
		sem_post(&sem);
	}
	return NULL;
}


int main(void)
{
	pthread_t mythread;
	int i;

	sem_init(&sem, 0, 1);//信号量初始化
	if(pthread_create(&mythread, NULL, thread_function, NULL))
	{
		printf("create thread error!\n");
		abort();
	}

/*	sleep(1);*/

	for(i = 0; i < 10; i++)
	{
		sem_wait(&sem);//=0
		myglobal = myglobal + 1;
		printf("o");
		fflush(stdout);
		sleep(1);
		sem_post(&sem);//=1
	}

	if(pthread_join(mythread, NULL))
	{
		printf("waiting thread failed!\n");
		abort();
	}

	printf("myglobal equals %d\n",myglobal);

	exit(0);
}

例子2:

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

void * thread_function(void * arg);
sem_t bin_sem;

#define WORK_SIZE 1024
char work_area[WORK_SIZE];

int main(int argc, char const *argv[])
{
	int res;
	pthread_t pthread;
	void * thread_result;

	res = sem_init(&bin_sem,0,0);//step 1
	if (res != 0)
	{
		perror("sem_init failed!");
		exit(EXIT_FAILURE);
	}

	res = pthread_create(&pthread,NULL,thread_function,NULL);//step 2
	if (res != 0)
	{
		perror("pthread_create failed!");
		exit(EXIT_FAILURE);
	}

	printf("input some text. Enter end to finish\n");
	while(strncmp("end",work_area,3) != 0)
	{	
		fgets(work_area,WORK_SIZE,stdin);
		sem_post(&bin_sem);//step 3
	}
	printf("the final work_area is : %s\n",work_area);//the result is end that reflect every time work_area overload
	res = pthread_join(pthread,&thread_result);
	if (res != 0)
	{
		perror("pthread_join failed!");
		exit(EXIT_FAILURE);
	}

	sem_destroy(&bin_sem);
	exit(EXIT_SUCCESS);
}

void *thread_function(void * arg)
{
	sem_wait(&bin_sem);//if sem value equals 0,and wait,else run 
	while(strncmp("end",work_area,3) != 0)
	{
		printf("you input %d characters\n",strlen(work_area)-1);//remove the enter key
		sem_wait(&bin_sem);//if sem value equals 0,and wait,else run 
	}
	pthread_exit(NULL);
}

保证线程之间的同步!

2015-08-03 19:28:47 Guyuebingchuan 阅读数 286
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5047 人正在学习 去看看 朱有鹏
信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量。相似地,线程同步是控制线程执行和访问临界区域的方法

一、什么是信号量
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。

二、信号量的接口和使用

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

1、sem_init函数
该函数用于创建信号量,其原型如下:
  1. int sem_init(sem_t *sem, int pshared, unsigned int value);  
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.

2、sem_wait函数
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:
  1. int sem_wait(sem_t *sem);  
sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

3、sem_post函数
该函数用于以原子操作的方式将信号量的值加1。它的原型如下:
  1. int sem_post(sem_t *sem);  
与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

4、sem_destroy函数
该函数用于对用完的信号量的清理。它的原型如下:
  1. int sem_destroy(sem_t *sem);  
成功时返回0,失败时返回-1.

三、使用信号量同步线程

下面以一个简单的多线程程序来说明如何使用信号量进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程等待直到有文本输入,然后调用sem_post来增加信号量的值,这样就会立刻使子线程从sem_wait的等待中返回并开始执行。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值。

  1. #include <unistd.h>  
  2. #include <pthread.h>  
  3. #include <semaphore.h>  
  4. #include <stdlib.h>  
  5. #include <stdio.h>  
  6. #include <string.h>  
  7.   
  8. //线程函数  
  9. void *thread_func(void *msg);  
  10. sem_t sem;//信号量  
  11.   
  12. #define MSG_SIZE 512  
  13.   
  14. int main()  
  15. {  
  16.     int res = -1;  
  17.     pthread_t thread;  
  18.     void *thread_result = NULL;  
  19.     char msg[MSG_SIZE];  
  20.     //初始化信号量,其初值为0  
  21.     res = sem_init(&sem, 0, 0);  
  22.     if(res == -1)  
  23.     {  
  24.         perror("semaphore intitialization failed\n");  
  25.         exit(EXIT_FAILURE);  
  26.     }  
  27.     //创建线程,并把msg作为线程函数的参数  
  28.     res = pthread_create(&thread, NULL, thread_func, msg);  
  29.     if(res != 0)  
  30.     {  
  31.         perror("pthread_create failed\n");  
  32.         exit(EXIT_FAILURE);  
  33.     }  
  34.     //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”  
  35.     printf("Input some text. Enter 'end'to finish...\n");  
  36.     while(strcmp("end\n", msg) != 0)  
  37.     {  
  38.         fgets(msg, MSG_SIZE, stdin);  
  39.         //把信号量加1  
  40.         sem_post(&sem);  
  41.     }  
  42.   
  43.     printf("Waiting for thread to finish...\n");  
  44.     //等待子线程结束  
  45.     res = pthread_join(thread, &thread_result);  
  46.     if(res != 0)  
  47.     {  
  48.         perror("pthread_join failed\n");  
  49.         exit(EXIT_FAILURE);  
  50.     }  
  51.     printf("Thread joined\n");  
  52.     //清理信号量  
  53.     sem_destroy(&sem);  
  54.     exit(EXIT_SUCCESS);  
  55. }  
  56.   
  57. void* thread_func(void *msg)  
  58. {  
  59.     //把信号量减1  
  60.     sem_wait(&sem);  
  61.     char *ptr = msg;  
  62.     while(strcmp("end\n", msg) != 0)  
  63.     {  
  64.         int i = 0;  
  65.         //把小写字母变成大写  
  66.         for(; ptr[i] != '\0'; ++i)  
  67.         {  
  68.             if(ptr[i] >= 'a' && ptr[i] <= 'z')  
  69.             {  
  70.                 ptr[i] -= 'a' - 'A';  
  71.             }  
  72.         }  
  73.         printf("You input %d characters\n", i-1);  
  74.         printf("To Uppercase: %s\n", ptr);  
  75.         //把信号量减1  
  76.         sem_wait(&sem);  
  77.     }  
  78.     //退出线程  
  79.     pthread_exit(NULL);  
  80. }  
运行结果如下:



从运行的结果来看,这个程序的确是同时在运行两个线程,一个控制输入,另一个控制处理统计和输出。

四、分析此信号量同步程序的缺陷
但是这个程序有一点点的小问题,就是这个程序依赖接收文本输入的时间足够长,这样子线程才有足够的时间在主线程还未准备好给它更多的单词去处理和统计之前处理和统计出工作区中字符的个数。所以当我们连续快速地给它两组不同的单词去统计时,子线程就没有足够的时间支执行,但是信号量已被增加不止一次,所以字符统计线程(子线程)就会反复处理和统计字符数目,并减少信号量的值,直到它再次变成0为止。

为了更加清楚地说明上面所说的情况,修改主线程的while循环中的代码,如下:
  1. printf("Input some text. Enter 'end'to finish...\n");  
  2. while(strcmp("end\n", msg) != 0)  
  3. {  
  4.     if(strncmp("TEST", msg, 4) == 0)  
  5.     {  
  6.         strcpy(msg, "copy_data\n");  
  7.         sem_post(&sem);  
  8.     }  
  9.     fgets(msg, MSG_SIZE, stdin);  
  10.     //把信号量加1  
  11.     sem_post(&sem);  
  12. }  
重新编译程序,此时运行结果如下:



当我们输入TEST时,主线程向子线程提供了两个输入,一个是来自键盘的输入,一个来自主线程复数据到msg中,然后从运行结果可以看出,运行出现了异常,没有处理和统计从键盘输入TEST的字符串而却对复制的数据作了两次处理。原因如上面所述。

五、解决此缺陷的方法

解决方法有两个,一个就是再增加一个信号量,让主线程等到子线程处理统计完成之后再继续执行;另一个方法就是使用互斥量。

下面给出用增加一个信号量的方法来解决该问题的代码,源文件名为semthread2.c,源代码如下:
  1. #include <unistd.h>  
  2. #include <pthread.h>  
  3. #include <semaphore.h>  
  4. #include <stdlib.h>  
  5. #include <stdio.h>  
  6. #include <string.h>  
  7.   
  8.   
  9. //线程函数  
  10. void *thread_func(void *msg);  
  11. sem_t sem;//信号量  
  12. sem_t sem_add;//增加的信号量  
  13.   
  14.   
  15. #define MSG_SIZE 512  
  16.   
  17.   
  18. int main()  
  19. {  
  20.     int res = -1;  
  21.     pthread_t thread;  
  22.     void *thread_result = NULL;  
  23.     char msg[MSG_SIZE];  
  24.     //初始化信号量,初始值为0  
  25.     res = sem_init(&sem, 0, 0);  
  26.     if(res == -1)  
  27.     {  
  28.         perror("semaphore intitialization failed\n");  
  29.         exit(EXIT_FAILURE);  
  30.     }  
  31.     //初始化信号量,初始值为1  
  32.     res = sem_init(&sem_add, 0, 1);  
  33.     if(res == -1)  
  34.     {  
  35.         perror("semaphore intitialization failed\n");  
  36.         exit(EXIT_FAILURE);  
  37.     }  
  38.     //创建线程,并把msg作为线程函数的参数  
  39.     res = pthread_create(&thread, NULL, thread_func, msg);  
  40.     if(res != 0)  
  41.     {  
  42.         perror("pthread_create failed\n");  
  43.         exit(EXIT_FAILURE);  
  44.     }  
  45.     //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”  
  46.     printf("Input some text. Enter 'end'to finish...\n");  
  47.       
  48.     sem_wait(&sem_add);  
  49.     while(strcmp("end\n", msg) != 0)  
  50.     {  
  51.         if(strncmp("TEST", msg, 4) == 0)  
  52.         {  
  53.             strcpy(msg, "copy_data\n");  
  54.             sem_post(&sem);  
  55.             //把sem_add的值减1,即等待子线程处理完成  
  56.             sem_wait(&sem_add);  
  57.         }  
  58.         fgets(msg, MSG_SIZE, stdin);  
  59.         //把信号量加1  
  60.         sem_post(&sem);  
  61.         //把sem_add的值减1,即等待子线程处理完成  
  62.         sem_wait(&sem_add);  
  63.     }  
  64.   
  65.   
  66.     printf("Waiting for thread to finish...\n");  
  67.     //等待子线程结束  
  68.     res = pthread_join(thread, &thread_result);  
  69.     if(res != 0)  
  70.     {  
  71.         perror("pthread_join failed\n");  
  72.         exit(EXIT_FAILURE);  
  73.     }  
  74.     printf("Thread joined\n");  
  75.     //清理信号量  
  76.     sem_destroy(&sem);  
  77.     sem_destroy(&sem_add);  
  78.     exit(EXIT_SUCCESS);  
  79. }  
  80.   
  81.   
  82. void* thread_func(void *msg)  
  83. {  
  84.     char *ptr = msg;  
  85.     //把信号量减1  
  86.     sem_wait(&sem);  
  87.     while(strcmp("end\n", msg) != 0)  
  88.     {  
  89.         int i = 0;  
  90.         //把小写字母变成大写  
  91.         for(; ptr[i] != '\0'; ++i)  
  92.         {  
  93.             if(ptr[i] >= 'a' && ptr[i] <= 'z')  
  94.             {  
  95.                 ptr[i] -= 'a' - 'A';  
  96.             }  
  97.         }  
  98.         printf("You input %d characters\n", i-1);  
  99.         printf("To Uppercase: %s\n", ptr);  
  100.         //把信号量加1,表明子线程处理完成  
  101.         sem_post(&sem_add);  
  102.         //把信号量减1  
  103.         sem_wait(&sem);  
  104.     }  
  105.     sem_post(&sem_add);  
  106.     //退出线程  
  107.     pthread_exit(NULL);  
  108. }  
其运行结果如下:


分析:这里我们多使用了一个信号量sem_add,并把它的初值赋为1,在主线程在使用sem_wait来等待子线程处理完全,由于它的初值为1,所以主线程第一次调用sem_wait总是立即返回,而第二次调用则需要等待子线程处理完成之后。而在子线程中,若处理完成就会马上使用sem_post来增加信号量的值,使主线程中的sem_wait马上返回并执行紧接下面的代码。从运行结果来看,运行终于正常了。注意,在线程函数中,信号量sem和sem_add使用sem_wait和sem_post函数的次序,它们的次序不能错乱,否则在输入end时,可能运行不正常,子线程不能正常退出,从而导致程序不能退出。

至于使用互斥量的方法,将会在下篇文章:Linux多线程——使用互斥量同步线程中详细介绍。
没有更多推荐了,返回首页