UNIX环境高级编程之----多线程同步学习

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。

1、互斥锁(mutex)

通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t*mutexattr);
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_destroy(pthread_mutex *mutex);
int pthread_mutex_unlock(pthread_mutex *

(1)先初始化锁init()或静态赋值pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIER

attr_t有:

PTHREAD_MUTEX_TIMED_NP:其余线程等待队列

PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争

PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;

PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争

(2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY

(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁

(4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用内存资源

看例子:

#include<iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std; 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

int tmp; 

void* thread(void *arg)
{
    cout << "thread id is " << pthread_self() << endl; 

    pthread_mutex_lock(&mutex); 

    tmp = 12; 

    cout << "Now a is " << tmp << endl; 

    pthread_mutex_unlock(&mutex); 

    return NULL;
} 

int main()
{
    pthread_t id; 

    cout << "main thread id is " << pthread_self() << endl; 

    tmp = 3; 

    cout << "In main func tmp = " << tmp << endl; 

    if (!pthread_create(&id, NULL, thread, NULL)) 

    {
        cout << "Create thread success!" << endl;
    }
    else
    {
        cout << "Create thread failed!" << endl;
    } 

    pthread_join(id, NULL); 

    pthread_mutex_destroy(&mutex); 

    return 0; 

}

编译方式1:g++ test.cpp -o test -lpthread

make方式:

ALL= test
LIB= -lpthread
objects= test.o
$(ALL) : $(objects)
        g++ -o [email protected] $(objects) $(LIB)
$(objects):%.o:%.cpp
        $(CC)  -c  $< -o [email protected]
.PHONY : clean
clean:
        rm $(objects)

运行结果:./test

[[email protected] linux_chen]$ ./test

main thread id is 3086866128

In main func tmp = 3

Create thread success!

thread id is 3086863248

Now a is 12

2、条件变量(cond)

利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

(1)初始化.init()或者pthread_cond_t
cond=PTHREAD_COND_INITIALIER(前者为动态初始化,后者为静态初始化);属性置为NULL

(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真,timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)

(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)

(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

对于

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

一定要在mutex的锁定区域内使用。

如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考

pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!

另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

说明:

(1)pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。

(2)互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。

(3)pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁,且pthread_cond_timedwait返回错误ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime
= 0 表示 1970年1月1日00:00:00
GMT。

(4)pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。

(5)条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁。

看例子1:

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

pthread_mutex_t mutex;
pthread_cond_t cond;

void hander(void *arg)
{
    free(arg);
    (void)pthread_mutex_unlock(&mutex);
}

void *thread1(void *arg)
{
     pthread_cleanup_push(hander, &mutex);
     while(1)
     {
         printf("thread1 is running\n");
         pthread_mutex_lock(&mutex);
         pthread_cond_wait(&cond,&mutex);
         printf("thread1 applied the condition\n");
         pthread_mutex_unlock(&mutex);
         sleep(4);
     }
     pthread_cleanup_pop(0);
} 

void *thread2(void *arg)
{
    while(1)
    {
        printf("thread2 is running\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        printf("thread2 applied the condition\n");
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

int main()
{
     pthread_t thid1,thid2;
     printf("condition variable study!\n");
     pthread_mutex_init(&mutex,NULL);
     pthread_cond_init(&cond,NULL);
     pthread_create(&thid1,NULL,thread1,NULL);
     pthread_create(&thid2,NULL,thread2,NULL);
     sleep(1);
     do
     {
         pthread_cond_signal(&cond);
     }while(1);
     sleep(20);
     pthread_exit(0);
     return 0;
}

make:

ALL= pthread
LIB= -lpthread
objects= testpthread1.o
$(ALL) : $(objects)
        g++ -o [email protected] $(objects) $(LIB)
$(objects):%.o:%.cpp
        $(CC)  -c  $< -o [email protected]
.PHONY : clean
clean:
        rm $(objects)

运行结果:

[[email protected] linux_chen]$ ./pthread

condition variable study!

thread1 is running

thread2 is running

thread1 applied the condition

thread2 applied the condition

thread2 is running

thread2 applied the condition

thread2 is running

thread2 applied the condition

thread2 is running

thread2 applied the condition

thread1 is running

thread1 applied the condition

thread2 is running

thread2 applied the condition

thread2 is running

thread2 applied the condition

thread2 is running

thread2 applied the condition

thread2 is running

thread2 applied the condition

...............................

看例子2:

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

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

struct node
{
     int n_number;
     struct node *n_next;
} *head = NULL; 

/*[thread_func]*/
static void cleanup_handler(void *arg)
{
     printf("Cleanup handler of second thread.\n");
     free(arg);
     (void)pthread_mutex_unlock(&mtx);
} 

static void *thread_func(void *arg)
{
     struct node *p = NULL;
     pthread_cleanup_push(cleanup_handler, p);
     while (1)
     {
         //这个mutex主要是用来保证pthread_cond_wait的并发性
         pthread_mutex_lock(&mtx);
         while (head == NULL)
         {
         //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何
         //这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线
         //程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
         //这个时候,应该让线程继续进入pthread_cond_wait
         // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
         //然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立
         //而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
         //用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/
         pthread_cond_wait(&cond, &mtx);
         p = head;
         head = head->n_next;
         printf("Get %d from front of queue\n", p->n_number);
         free(p);
          }
          pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
     }
     pthread_cleanup_pop(0);
     return 0;
} 

int main(void)
{
     pthread_t tid;
     int i;
     struct node *p;
     //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而
     //不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
     pthread_create(&tid, NULL, thread_func, NULL);
     sleep(1);
     for (i = 0; i < 10; i++)
     {
         p = (struct node*)malloc(sizeof(struct node));
         p->n_number = i;
         pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,
         p->n_next = head;
         head = p;
         pthread_cond_signal(&cond);
         pthread_mutex_unlock(&mtx); //解锁
         sleep(1);
     }
     printf("thread 1 wanna end the line.So cancel thread 2.\n"); 

     //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出
     //线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
     pthread_cancel(tid);
     pthread_join(tid, NULL);
     printf("All done -- exiting\n");
     return 0;
}

make:

ALL= pthread
LIB= -lpthread
objects= testpthread2.o
$(ALL) : $(objects)
        g++ -o [email protected] $(objects) $(LIB)
$(objects):%.o:%.cpp
        $(CC)  -c  $< -o [email protected]
.PHONY : clean
clean:
        rm $(objects)

运行结果:

[[email protected] linux_chen]$ ./pthread

Get 0 from front of queue

Get 1 from front of queue

Get 2 from front of queue

Get 3 from front of queue

Get 4 from front of queue

Get 5 from front of queue

Get 6 from front of queue

Get 7 from front of queue

Get 8 from front of queue

Get 9 from front of queue

thread 1 wanna end the line.So cancel thread 2.

Cleanup handler of second thread./nAll done -- exiting

3、信号量

    如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。

    信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。

#include

int sem_init (sem_t *sem , int pshared, unsigned int value);

这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

两个原子操作函数:

int sem_wait(sem_t *sem);

int sem_post(sem_t *sem);

这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。

sem_post:给信号量的值加1;

sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。

int sem_destroy(sem_t *sem);

这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。

看例子:

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

#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;} 

typedef struct _PrivInfo
{
  sem_t s1;
  sem_t s2;
  time_t end_time;
}PrivInfo; 

static void info_init (PrivInfo* thiz);
static void info_destroy (PrivInfo* thiz);
static void* pthread_func_1 (PrivInfo* thiz);
static void* pthread_func_2 (PrivInfo* thiz); 

int main (int argc, char** argv)
{
  pthread_t pt_1 = 0;
  pthread_t pt_2 = 0;
  int ret = 0;
  PrivInfo* thiz = NULL; 

  thiz = (PrivInfo* )malloc (sizeof (PrivInfo));
  if (thiz == NULL)
  {
    printf ("[%s]: Failed to malloc priv./n");
    return -1;
  } 

  info_init (thiz); 

  ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);
  if (ret != 0)
  {
    perror ("pthread_1_create:");
  } 

  ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);
  if (ret != 0)
  {
     perror ("pthread_2_create:");
  } 

  pthread_join (pt_1, NULL);
  pthread_join (pt_2, NULL); 

  info_destroy (thiz); 

  return 0;
} 

static void info_init (PrivInfo* thiz)
{
  return_if_fail (thiz != NULL); 

  thiz->end_time = time(NULL) + 10; 

  sem_init (&thiz->s1, 0, 1);
  sem_init (&thiz->s2, 0, 0); 

  return;
} 

static void info_destroy (PrivInfo* thiz)
{
  return_if_fail (thiz != NULL); 

  sem_destroy (&thiz->s1);
  sem_destroy (&thiz->s2); 

  free (thiz);
  thiz = NULL; 

  return;
} 

static void* pthread_func_1 (PrivInfo* thiz)
{
  return_if_fail (thiz != NULL); 

  while (time(NULL) < thiz->end_time)
  {
    sem_wait (&thiz->s2);
    printf ("pthread1: pthread1 get the lock./n"); 

    sem_post (&thiz->s1);
    printf ("pthread1: pthread1 unlock/n"); 

    sleep (1);
  } 

  return;
} 

static void* pthread_func_2 (PrivInfo* thiz)
{
  return_if_fail (thiz != NULL); 

  while (time (NULL) < thiz->end_time)
  {
    sem_wait (&thiz->s1);
    printf ("pthread2: pthread2 get the unlock./n"); 

    sem_post (&thiz->s2);
    printf ("pthread2: pthread2 unlock./n"); 

    sleep (1);
  } 

  return;
}

参考:

【1】 http://www.cnblogs.com/feisky/archive/2009/11/12/1601824.html

【2】 http://www.cnblogs.com/mydomain/archive/2011/07/10/2102147.html

【3】
线程函数介绍

http://www.unix.org/version2/whatsnew/threadsref.html

【4】 http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html

【5】
线程常用函数简介

http://www.rosoo.net/a/201004/8954.html

【6】
条件变量

http://blog.csdn.net/hiflower/article/details/2195350

【7】条件变量函数说明

http://blog.csdn.net/hairetz/article/details/4535920

本文来自博文:

http://www.cnblogs.com/mydomain/archive/2011/08/14/2138455.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-09 02:20:53

UNIX环境高级编程之----多线程同步学习的相关文章

UNIX环境高级编程之----多线程技术(1)

进程是系统中程序执行和资源分配的基本单位.每个进程都有自己的数据段,代码段和堆栈段,这就导致了进程在进行切换等操作起到了现场保护作用.但是为了进一步减少处理机的空转时间支持多处理器和减少上下文切换开销,进程演化中出现了另外一个概念,这就是线程,也被人称为轻量级的进程.它是一个进程内的基本调度单位.线程是在共享的内存空间中并发的多道执行路径,它们共享一个进程的资源,比如文件描述符和信号处理等.因此, 大大减少了上下文切换的开销. 线程跟进程一样,都拥有一张控制表,线程将相关的变量值放在线程控制表中

UNIX环境高级编程之----多线程技术(3)

pthread_create函数的第二个参数,是关于线程属性的设置,这也是今天所有讲述的.这些属性主要包括邦定属性.分离属性.堆栈地址.堆栈大小.优先级.其中系统默认的是非邦定.非分离.缺省1M的堆栈.与父进程同样级别的优先级.在pthread_create中,把第二个参数设置为NULL的话,将采用默认的属性配置. (1)邦定属性. 在LINUX中,采用的是"一对一"的线程机制.也就是一个用户线程对应一个内核线程.邦定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU时间片的调

OS X下UNIX环境高级编程(第三版)学习日志-第一章ChapterI,编译apue包与第一个例程

1.从网络上获取代码,地址如下apue最新官方下载地址 2.解压到本地 由于最新版本是第三版(3rd Edition),apue.3e,就是我们要的源代码 3.编译源代码 编译过程中笔者并未遇到任何问题,所以建议大家也下载最新版本的代码来学习,贴一下笔者的环境信息 Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple

UNIX环境高级编程学习笔记(第一章UNIX基础知识)

总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时候使用的平台是Windows+VMware+debian,使用secureCRT来连接(可以实现多个终端连接). 因为第一章是本书大概的描述,所以第一章的我打算写得详细一点,而且书本的原话占的比例会比较多,重点的东西会用粗体显示出来. 1.1  引言 所有操作系统都为他们所运行的程序提供服务.典型的

《UNIX环境高级编程》学习心得 一

本文内容大部分摘自<UNIX环境高级编程>,附有部分个人心得. 1.unix体系结构 从严格意义上来说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境.我们通常将这种软件称为内核(kernel),因为它相对较小,而且位于环境核心.如图显示unix体系结构. 内核接口被称为系统调用(system call).公用函数库构建在系统调用接口之上,应用程序既可食用公用函数库,也可以使用系统调用.shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口. 从广义上来讲,操作系

(九) 一起学 Unix 环境高级编程 (APUE) 之 线程

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

《UNIX环境高级编程(第3版)》

<UNIX环境高级编程(第3版)> 基本信息 原书名:Advanced Programming in the UNIX Environment (3rd Edition) (Addison-Wesley Professional Computing Series) 原出版社: Addison-Wesley Professional 作者: (美)W. Richard Stevens    Stephen A. Rago 译者: 戚正伟 张亚英 尤晋元 出版社:人民邮电出版社 ISBN:9787

Unix 环境高级编程 (APUE) 之 网络 IPC:套接字

一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字 . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APU

【转】apue《UNIX环境高级编程第三版》第一章答案详解

原文网址:http://blog.csdn.net/hubbybob1/article/details/40859835 大家好,从这周开始学习apue<UNIX环境高级编程第三版>,在此,我要感谢网易的一个工程师朋友和室友,没有他们,我不会开始真正的学习这本书,希望大家以后开始慢慢进步.废话少说,直接上课后习题了. UNIX高级编程第一章习题答案: 1.1在系统上验证,除根目录外,目录l和l l是不同的. 答:这个验证有很多方法可使用命令ls .cd.vim等,目录.指向当前目录,目录..指