Linux多线程编程

 

    线程(thread)是包含在进程内部的顺序执行流,是进程中的实际运作单位,也是操作系统能够进行调度的最小单位。一个进程中可以并发多条线程,每条线程并行执行不同的任务。

线程与进程的关系

  线程与进程的关系可以归结于以下几点:

  一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个主线程;
  资源分配给进程,同一进程的所有线程共享该进程的所有资源;
  线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
  进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
  在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销大于创建或撤消线程时的开销。

为什么要使用多线程

  多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对以多进程程序结构有以下的优势:

  (1)方便的通信和数据交换

  线程间有方便的通信和数据交换机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

  (2)更高效的利用CPU

  使用多线程可以提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,可以避免这种尴尬的情况。

  同时多线程使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

  编写Pthreads多线程程序时源码只需包含pthread.h头文件就可以使用Pthreads库中的所有类型及函数:

#include <pthread.h>

  在编译Pthread程序时在编译和链接过程中需要加上-pthread参数:

LDFLAGS += -pthread

线程管理

  线程管理包含了线程的创建、终止、等待、分离、设置属性等操作。

线程ID

  线程ID 可以看作为线程的句柄,用来引用一个线程。

  Pthreads线程有一个pthread_t类型的ID来引用。线程可以通过调用pthread_self()函数来获取自己的ID。pthread_self()函数原型如下:

pthread_t pthread_self(void);

  该函数返回调用线程的线程ID。

  由于pthread_t类型可能是一个结构体,可以使用pthread_equal()来比较两个线程ID是否相等。pthread_equal()函数原型如下:

int pthread_equal(pthread_t t1, pthread_t t2);

  如果t1等于t2,该函数返回一个非0值,否则返回0。

创建与终止

  每个线程都有从创建到终止的生命周期。

创建线程

  在进程中创建一个新线程的函数是pthread_create(),原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

  返回值说明:

  • 如果pthread_create()调用成功,函数返回0,否则返回一个非0的错误码,表1列出pthread_create()函数调用时必须检查的错误码。
  • thread用指向新创建的线程的ID;
  • attr用来表示一个封装了线程各种属性的属性对象,如果attr为NULL,新线程就使用默认的属性,线程属性节将讨论线程属性的细节;
  • start_routine是线程开始执行的时候调用的函数的名字,start_routine函数有一个有指向void的指针参数,并有pthread_create的第四个参数arg指定值,同时start_routine函数返回一个指向void的指针,这个返回值被pthread_join当做退出状态处理,连接与分离节介绍线程的退出状态;
  • arg为参数start_routine指定函数的参数。

终止线程

  进程的终止可以通过直接调用exit()、执行main()中的return、或者通过进程的某个其它线程调用exit()来实现。在以上任何一种情况下,所有的线程都会终止。如果主线程在创建了其它线程后没有任务需要处理,那么它应该阻塞等待所有线程都结束为止,或者应该调用pthread_exit(NULL)。

  调用exit()函数会使整个进程终止,而调用pthread_exit()只会使得调用线程终止,同时在创建的线程的顶层执行return线程会隐式地调用pthread_exit()。pthread_exit()函数原型如下:

void pthread_exit(void *retval);

  retval是一个void类型的指针,可以将线程的返回值当作pthread_exit()的参数传入,这个值同样被pthread_join()当作退出状态处理。如果进程的最后一个线程调用了pthread_exit(),进程会带着状态返回值0退出。

  程序给出了线程创建和终止的示例程序,主线程创建了5个线程,这5个线程和主线程并发执行,主线程创建完线程后调用pthread_exit()函数退出线程,其它线程分别打印当前线程的序号。当主线程先于其它进程执行pthread_exit()时,进程不会退出,而是最后一个线程完成时才会进程退出。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid){ /* 线程函数 */
long tid;
tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid); /* 打印线程对应的参数 */
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc;
long t;
for(t=0; t<NUM_THREADS; t++){ /*循环创建5个线程 */
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); /* 创建线程 */
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
printf("In main: exit!\n");
pthread_exit(NULL); /* 主线程退出 */
return 0;
}

  程序运行结果如下图所示,程序中主线程调用了pthread_exit()函数并不会将整个进程终止,而是最后一个线程调用pthread_exit()时程序才完成运行。

118c5ab6c49b1b3649cf4362f2a7b088.png

 

  • 线程属性

  前面介绍的线程创建pthread_create()函数,pthread_create()函数的第二个参数为pthread_attr_t类型,用于设置线程的属性。

  线程基本属性包括:栈大小、调度策略和线程状态。

  通常先创建一个属性对象,然后在属性对象上设置属性的值,再将属性对象传给pthread_create函数的第二个参数用来创建含有该属性的线程。

  一个属性对象可以多次传给pthread_create()函数创建多个含有相同属性的线程。

属性对象

  (1)初始化属性对象

  pthread_attr_init()函数用于将属性对象使用默认值进行初始化,函数原型如下:

int pthread_attr_init(pthread_attr_t *attr);

  函数只有一个参数,是一个指向pthread_attr_t的属性对象的指针。成功返回0,否则返回一个非0的错误码。

  (2)销毁属性对象

  销毁属性对象使用pthread_attr_destroy()函数,函数原型如下:

int pthread_attr_destroy(pthread_attr_t *attr);

  函数只有一个参数,是一个指向pthread_attr_t的属性对象的指针。成功返回0,否则返回一个非0的错误码。

线程状态

  线程有两种线程状态,取值可能是:

  • PTHREAD_CREATE_JOINABLE——非分离线程;
  • PTHREAD_CREATE_DETACHED——分离线程。

  (1)获取线程状态

  获取线程状态的函数是pthread_attr_getdetachstate(),原型如下:

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

  参数attr是一个指向已初始化的属性对象的指针,detachstate是获取结果值的指针。成功返回0,否则返回一个非0的错误码。

  (2)设置线程状态

  设置线程状态的函数是pthread_attr_setdetachstate(),原型如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  参数attr是一个指向已初始化的属性对象的指针, detachstate是要设置的值。成功返回0,否则返回一个非0的错误码。

  • 线程栈

  每个线程都有一个独立调用栈,线程的栈大小在线程创建的时候就已经固定下来,Linux系统线程的默认栈大小为8MB,只有主线程的栈大小会在运行过程中自动增长。用户可以通过属性对象来设置和获取栈大小。

  (1)获取线程栈

  获取线程栈大小的函数是pthread_attr_getstacksize(),原型如下:

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

  参数attr都是一个指向已初始化的属性对象的指针,stacksize是获取的栈大小的指针。成功返回0,否则返回一个非0的错误码。

  (2)设置线程栈

  设置线程栈大小的函数是pthread_attr_setstacksize(),原型如下:

intpthread_attr_setstacksize(pthread_attr_t *attr, size_tstacksize);

  参数attr都是一个指向已初始化的属性对象的指针,stacksize是设置的栈大小。成功返回0,否则返回一个非0的错误码。

线程范例3

  程序清单3举例说明了线程创建及线程属性的使用方法,主线程根据参数列表的参数给出的线程栈大小来设置线程属性对象,然后为参数列表的剩余参数分别创建线程来实现小写转大写的功能及打印栈地址。

线程属性设置

#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#define handle_error_en(en, msg) \ /* 出错处理宏供返回错误码的函数使用 */
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \ /* 出错处理宏 */
do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct thread_info {
pthread_t thread_id;
int thread_num;
char *argv_string;
};

static void *thread_start(void *arg){ /* 线程运行函数 */
struct thread_info *tinfo = arg;
char *uargv, *p;

printf("Thread %d: top of stack near %p; argv_string=%s\n", /*通过p的地址来计算栈的起始地址 */
tinfo->thread_num, &p, tinfo->argv_string);
uargv = strdup(tinfo->argv_string);
if (uargv == NULL)
handle_error("strdup");

for (p = uargv; *p != '\0'; p++)
*p = toupper(*p); /*小写字符转换大写字符 */

return uargv; /*将转换结果返回 */
}
intmain(int argc, char *argv[]){
int s, tnum, opt, num_threads;
struct thread_info *tinfo;
pthread_attr_t attr;
int stack_size;
oid *res;

stack_size = -1;
while ((opt = getopt(argc, argv, "s:")) != -1) { /* 处理参数-s所指定的栈大小 */
switch (opt) {
case 's':
stack_size = strtoul(optarg, NULL, 0);
break;
default:
fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",argv[0]);
exit(EXIT_FAILURE);
}
}
num_threads = argc - optind;

s = pthread_attr_init(&attr); /* 初始化属性对象 */
if (s != 0)
handle_error_en(s, "pthread_attr_init");
if (stack_size > 0) {
s = pthread_attr_setstacksize(&attr, stack_size); /* 设置属性对象的栈大小 */
if (s != 0)
handle_error_en(s, "pthread_attr_setstacksize");
}

tinfo = calloc(num_threads, sizeof(struct thread_info));
if (tinfo == NULL)
handle_error("calloc");
for (tnum = 0; tnum < num_threads; tnum++) {
tinfo[tnum].thread_num = tnum + 1;
tinfo[tnum].argv_string = argv[optind + tnum];
s = pthread_create(&tinfo[tnum].thread_id, &attr, /* 根据属性创建线程 */
&thread_start, &tinfo[tnum]);
if (s != 0)
handle_error_en(s, "pthread_create");
}
s = pthread_attr_destroy(&attr); /* 销毁属性对象 */
if (s != 0)
handle_error_en(s, "pthread_attr_destroy");

for (tnum = 0; tnum < num_threads; tnum++) {
s = pthread_join(tinfo[tnum].thread_id, &res); /* 等待线程终止,并获取返回值 */
if (s != 0)
handle_error_en(s, "pthread_join");
printf("Joined with thread %d; returned value was %s\n",
tinfo[tnum].thread_num, (char *) res);
free(res);
}
free(tinfo);
exit(EXIT_SUCCESS);
}

  运行结果,运行此程序是使用-s参数指定每个创建线程的栈大小,每个线程运行起来后都先取栈变量的地址用过打印变量地址来大概估计栈起始的地址。然后每个线程将线程参数给出的字符串转换为大写并返回给主线程,主线程使用pthread_join()等待并获取线程的结果。

d12e96ef34b8393b5cc2867dcb62db53.png

  • 互斥量

  在计算机系统中有许多共享资源不允许用户并行使用。例如打印机设备,如果它同时进行两份文档打印,它的输出就会产生交错,从而都无法获得正确的文档。像打印机这样的共享设备被称为“排它性资源”,因为它一次只能由一个执行流访问。执行流必须以互斥的方式执行访问排它性资源的代码。

  临界区是必须以互斥方式执行的代码段,也就是说在临界区的范围内只能有一个活动的执行线程。

什么是互斥量

  互斥量(Mutex),又称为互斥锁,是一种用来保护临界区的特殊变量,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:

  • 如果互斥锁是锁定的,就是一个特定的线程持有这个互斥锁;
  • 如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。

  每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时,一个线程试图获取这个互斥锁时,这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时,一个线程试图获取这个互斥锁时,这个线程将阻塞在互斥锁的等待线程队列内。

  互斥量是最简单也是最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。另外,互斥量只能被短时间地持有,使用完临界资源后应立即释放锁。

创建与销毁

创建互斥量

  pthreads使用pthread_mutex_t类型的变量来表示互斥量,同时在使用互斥量进行同步时需要先对它进行初始化,可以静态或动态方式对互斥量进行初始化。

  (1)静态初始化

  对是静态分配的pthread_mutex_t变量来说值需要将PTHREAD_MUTEX_INITIALIZER赋给变量就行了。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  (2)动态初始化

  对动态分配或者不使用默认互斥属性的互斥变量来说,需要调用pthread_mutex_int()函数来执行初始化工作。pthread_mutex_int()函数原型如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

  参数mutex是一个指向要初始化的互斥量的指针;参数attr传递NULL来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。

 静态初始化程序通常比调用pthread_mutex_init更有效,而且在任何线程开始执行之前,确保变量被执行一次。

  以下代码用来动态的初始化默认属性的互斥量mylock:

int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_init(&mylock, NULL))
fprintf(stderr, "Failed to initialize mylock : %s\n", strerror(error));

销毁互斥量

  销毁互斥量使用pthread_mutex_destroy()函数,原型如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

  参数mutex指向要销毁的互斥量的指针。以下代码销毁了mylock互斥量:

int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_destroy(&mylock))
fprintf(stderr, "Failed to destroy mylock : %s\n", strerror(error));

加锁与解锁

加锁

  线程试图锁定互斥量的过程称之为加锁。

  Pthreads中有两个试图锁定互斥量的函数,pthread_mutex_lock()和pthread_mutex_trylock()。pthread_mutex_lock()函数会一直阻塞到互斥量可用为止,而pthread_mutex_trylock()会尝试加锁,通常立即返回。函数原型如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

  参数mutex是需要加锁的互斥量。函数成功返回0,否则返回一个非0的错误码,其中另一个线程已持有锁的情况下,调用pthread_mutex_trylock()函数是错误码为EBUSY。

解锁

  解锁是线程将互斥量由锁定状态变为解锁状态。

  pthread_mutex_unlock()函数用来释放指定的互斥量。函数原型如下:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

  参数mutex是需要加锁的互斥量。函数成功返回0,否则返回一个非0的错误码。

  只有当线程需要进入临界区之前正确地获取适当的互斥量,并在线程离开临界区时释放互斥量。以下伪代码展示了互斥量保护临界区的基本用法:

  1. pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
  2. pthread_mutex_lock(&mylock);
  3. 临界区代码
  4. pthread_mutex_unlock(&mylock);

  以下程序是使用互斥量来保证多线程同时输出顺序例子,互斥量保证获取的线程打印完才让别的线程打印,杜绝了打印乱序的问题。

#include<stdio.h>
2 #include<string.h>
3 #include<pthread.h>
4 #include<stdlib.h>
5 #include<unistd.h>
6
7 pthread_t tid[2];
8 pthread_mutex_t lock;
9
10 void* doPrint(void *arg)
11 {
12 int id = (long)arg;
13 int i = 0;
14 pthread_mutex_lock(&lock); /*使用互斥量保护临界区 */
15 printf("Job %d started\n", id);
16 for (i = 0; i < 5; i++) {
17 printf("Job %d printing\n", id);
18 usleep(10);
19 }
20 printf("Job %d finished\n", id);
21 pthread_mutex_unlock(&lock);
22 return NULL;
23 }
24
25 int main(void)
26 {
27 long i = 0;
28 int err;
29
30 if (pthread_mutex_init(&lock, NULL) != 0) /*动态初始化互斥量 */
31 {
32 printf("\n Mutex init failed\n");
33 return 1;
34 }
35 while(i < 2)
36 {
37 err = pthread_create(&(tid[i]), NULL, &doPrint, (void*)i);
38 if (err != 0)
39 printf("Can't create thread :[%s]", strerror(err));
40 i++;
41 }
42 pthread_join(tid[0], NULL);
43 pthread_join(tid[1], NULL);
44 pthread_mutex_destroy(&lock);
45
46 return 0;
47 }

  运行结果,可以看到Job 1先获取互斥锁并进行打印,所有Job 1需要打印的完成后才让Job 0打印。

ab5bb26f3d98280cce0ad08fc534bb88.png

  下图是该代码注释掉第14行和第21行后得到(即不使用互斥量)。由图可见,在无锁的情况下,该代码的输出乱序了。

  • 什么是死锁

  死锁是指两个或两个以上的执行序在执行过程中,因争夺资源而造成的一种互相等待的现象。例如:一个线程T1已锁定了一个资源R1,又想去锁定资源R2,而此时另一个线程T2已锁定了资源R2,却想去锁定资源R1,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况,如图6所示。

34c9bbbc3b9b2d80874a1c6e85804827.png

  以下程序示例了死锁发生的情况,程序创建了两个线程,第一个线程先获取mutexA锁,再获取mutexB锁;第二个线程先获取mutexB后获取mutexA,这时死锁就可能发生。

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

pthread_t tid[2];
pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;

void * t1(void *arg) {
pthread_mutex_lock(&mutexA); /* 线程1获取mutexA */
printf("t1 get mutexA\n");
usleep(1000);
pthread_mutex_lock(&mutexB); /* 线程1获取mutexB */
printf("t1 get mutexB\n");
pthread_mutex_unlock(&mutexB); /* 线程1释放mutexB */
printf("t1 release mutexB\n");
pthread_mutex_unlock(&mutexA); /* 线程1释放mutexA */
printf("t1 release mutexA\n");
return NULL;
}

void * t2(void *arg) {
pthread_mutex_lock(&mutexB);
printf("t2 get mutexB\n");
usleep(1000);
pthread_mutex_lock(&mutexA);
printf("t2 get mutexA\n");
pthread_mutex_unlock(&mutexA);
printf("t2 release mutexA\n");
pthread_mutex_unlock(&mutexB);
printf("t2 release mutexB\n");
return NULL;
}

int main(void) {
int err;

err = pthread_create(&(tid[0]), NULL, &t1, NULL ); /* 创建线程1 */
if (err != 0)
printf("Can't create thread :[%s]", strerror(err));

err = pthread_create(&(tid[1]), NULL, &t2, NULL); /* 创建线程2 */
if (err != 0)
printf("Can't create thread :[%s]", strerror(err));
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}

  运行结果,t1线程获取mutexA后等待mutexB,t2线程获取mutexB后等待mutexA,两个线程互相等待,进入死锁。

28d7845038a22f8b1fa78019e96746a7.png

  • 死锁的避免

  当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生,如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。例如,规定程序内有三个互斥锁的加锁顺序为mutexA->mutexB->mutexC,则线程t1、t2、t3线程操作伪代码如下所示:

  1. t1 t2 t3
  2. lock(mutexA) lock(mutexA) lock(mutexB)
  3. lock(mutexB) lock(mutexC) lock(mutexC)
  4. lock(mutexC)
  • 条件变量

       为什么需要条件变量?在多线程编程中仅使用互斥锁来完成互斥是不够用的,如以下情形:

  假设有两个线程t1 和 t2,需要这个两个线程循环对一个共享变量sum进行自增操作,那么t1 和t2只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:

pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;

void * t1t2(void) {
pthread_mutex_lock(&sumlock);
sum++;
pthread_mutex_unlock(&sumlock);
}

  如果这时需要增加另一个线程t3,需要t3在count 大于100时将count值重新置0值,那么可以t3可以实现如下:

void * t3 (void) {
pthread_mutex_lock(&sumlock);
if (sum >= 100) {
sum = 0;
pthread_mutex_unlock(&sumlock);
} else {
pthread_mutex_unlock(&sumlock);
usleep(100);
}
}

  以上代码存在以下问题:

  1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep()。这浪费了CPU处理时间。

  2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候usleep()一段时间。这样却又带来另外一个问题,亦即t3响应速度下降。可能在sum到达200的时候,t3才会醒过来。

  这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。

  • 创建与销毁

创建条件变量

  pthreads用pthread_cond_t类型的变量来表示条件变量。程序必须在使用pthread_cond_t变量之前对其进行初始化。

  (1)静态初始化

  对于静态分配的变量可以简单地将PTHREAD_COND_INITIALIZER赋值给变量来初始化默认行为的条件变量。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

  (2)动态初始化

  对动态分配或者不使用默认属性的条件变量来说可以使用pthread _cond_init()来初始化。函数原型如下:

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);

  参数cond是一个指向需要初始化pthread_cond_t变量的指针,参数attr传递NULL值时,pthread_cond_init()将cond初始化为默认属性的条件变量。

  静态初始化程序通常比调用pthread_cond_init()更有效,而且在任何线程开始执行之,确保变量被执行一次。

  以下代码示例了条件变量的初始化。

pthread_cond_t cond;
int error;
if (error = pthread_cond_init(&cond, NULL));
fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));

销毁条件变量

  函数pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

  函数成功调用返回0,否则返回一个非0的错误码。以下代码演示了如何销毁一个条件变量。

pthread_cond_t cond;
int error;
if (error = pthread_cond_destroy(&cond))
fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
  • 等待与通知

等待

  条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。

  条件等待函数有pthread_cond_wait()pthread_cond_timedwait()和两个,函数原型如下:

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

  pthread_cond_wait()函数在条件不满足时将一直等待,而pthread_cond_timedwait()将只等待一段时间。

  参数cond是一个指向条件变量的指针,参数mutex是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。pthread_timedwait()的第三个参数abstime是一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出,abstime是个绝对时间,而不是时间间隔。

  以上函数成功调用返回0,否则返回非0的错误码,其中pthread_cond_timedwait()函数如果abstime指定的时间到期,错误码为ETIMEOUT。

  以下代码使得线程进入等待,直到收到通知并且满足a 大于等于b的条件。

pthread_mutex_lock(&mutex)
while(a < b)
pthread_cond_wait(&cond, &mutex)
pthread_mutex_unlock(&mutex)

通知

  当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。

  条件通知函数有pthread_cond_signal()和pthread_cond_broadcast()函数,其中pthread_cond_signal函数可以唤醒一个在条件变量等待队列等待的线程,而pthread_cond_broadcast函数可以所有在条件变量等待队列等待的线程。函数原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

  参数cond是一个指向条件变量的指针。函数成功返回0,否则返回一个非0的错误码。

  “为什么需环境变量”节提出的问题的条件变量实现版本如程序所示(死锁的避免)。

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

pthread_t tid[3];
int sum = 0;
pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */
pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 静态初始化条件变量 */

void * t1t2(void *arg) {
int i;
long id = (long)arg;

for (i = 0; i < 60; i++) {
pthread_mutex_lock(&sumlock); /* 使用互斥量保护临界变量 */
sum++;
printf("t%ld: read sum value = %d\n", id + 1 , sum);
pthread_mutex_unlock(&sumlock);
if (sum >= 100)
pthread_cond_signal(&cond_sum_ready); /* 发送条件通知,唤醒等待线程 */
}
return NULL;
}
void * t3(void *arg) {
pthread_mutex_lock(&sumlock);
while(sum < 100) /* 不满足条件将一直等待 */
pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待条件满足 */
sum = 0;
printf("t3: clear sum value\n");
pthread_mutex_unlock(&sumlock);
return NULL;
}

int main(void) {
int err;
long i;

for (i = 0; i < 2; i++) {
err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i); /* 创建线程1线程2 */
if (err != 0) {
printf("Can't create thread :[%s]", strerror(err));
}
}
err = pthread_create(&(tid[2]), NULL, &t3, NULL); /* 创建线程3 */
if (err != 0)
printf("Can't create thread :[%s]", strerror(err));
for (i = 0; i < 3; i++)
pthread_join(tid[i], NULL);
return 0;
}

  运行结果如图所示。尽管程序会在sum累加到100时发送条件通知,但正如图中红框出所示,当sum计算到113时t3才可能被调用,这是因为signal与wait两个调用之间存在可能的间隙。

68c59d8f4a7106423bdb62db4e9a6be9.png

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小枭码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值