高级Linux程序设计第四章:线程

  • 要想使用POSIX标准线程API(pthreads),需要连接libpthread.so库到程序中。

1、创建线程

  • 进程中的每个线程都有一个线程号,类型为pthread_t。

  • 用pthread_self函数可以返回当前线程的线程号。

  • 线程号之间的比较可以用函数pthread_equal。

if (!pthread_equal (pthread_self (), other_thread))

    pthread_join (other_thread, NULL);

  • 每个线程执行一个线程函数:

void * function(void *)

  • 用函数pthread_create函数可以创建一个线程:

    • 第一个参数是一个pthread_t类型的变量的指针,用于存储线程号。

    • 第二个参数是一个pthread_attr_t类型的线程属性对象,NULL表示使用默认属性

    • 第三个参数是一个指向线程函数的指针

    • 第四个参数是void指针,指向传给线程的参数

  • 编译并链接程序:

 

% cc -o thread-create thread-create.c –lpthread

  • 线程退出有两种方式:

    • 线程函数退出。线程函数的返回值就是线程的返回值。

    • 显式调用pthread_exit,其参数为线程的返回值。

#include <pthread.h>

#include <stdio.h>

/* Prints x’s to stderr. The parameter is unused. Does not return. */

void* print_xs (void* unused)

{

    while (1)

        fputc (‘x’, stderr);

    return NULL;

}

/* The main program. */

int main ()

{

    pthread_t thread_id;

    /* Create a new thread. The new thread will run the print_xs function. */

    pthread_create (&thread_id, NULL, &print_xs, NULL);

    /* Print o’s continuously to stderr. */

    while (1)

        fputc (‘o’, stderr);

    return 0;

}

1.1、向线程传递数据

Listing 4.2 (thread-create2) Create Two Threads

#include <pthread.h>

#include <stdio.h>

/* Parameters to print_function. */

struct char_print_parms

{

    /* The character to print. */

    char character;

    /* The number of times to print it. */

    int count;

};

/* Prints a number of characters to stderr, as given by PARAMETERS,

which is a pointer to a struct char_print_parms. */

void* char_print (void* parameters)

{

    /* Cast the cookie pointer to the right type. */

    struct char_print_parms* p = (struct char_print_parms*) parameters;

    int i;

    for (i = 0; i < p->count; ++i)

        fputc (p->character, stderr);

    return NULL;

}

/* The main program. */

int main ()

{

    pthread_t thread1_id;

    pthread_t thread2_id;

    struct char_print_parms thread1_args;

    struct char_print_parms thread2_args;

    /* Create a new thread to print 30,000 ’x’s. */

    thread1_args.character = ’x’;

    thread1_args.count = 30000;

    pthread_create (&thread1_id, NULL, &char_print, &thread1_args);

    /* Create a new thread to print 20,000 o’s. */

    thread2_args.character = ’o’;

    thread2_args.count = 20000;

    pthread_create (&thread2_id, NULL, &char_print, &thread2_args);

    return 0;

}

此程序有一个Bug,主线程创建线程的时候,穿进去的参数是局部变量,如果主线程在从线程之前退出,则局部变量会被释放,然而从线程却可能仍然要访问这些变量,就会出错。因而应该应用join使得主线程等待从线程结束。

1.2、Join线程

  • phread_join有两个参数:

    • 第一个参数是要join的线程号。

    • 第二个参数是一个void指针,指向一个变量来接收线程的返回值。

 

Revised Main Function for thread-create2.c

int main ()

{

    pthread_t thread1_id;

    pthread_t thread2_id;

    struct char_print_parms thread1_args;

    struct char_print_parms thread2_args;

    /* Create a new thread to print 30,000 x’s. */

    thread1_args.character = ’x’;

    thread1_args.count = 30000;

    pthread_create (&thread1_id, NULL, &char_print, &thread1_args);

    /* Create a new thread to print 20,000 o’s. */

    thread2_args.character = ’o’;

    thread2_args.count = 20000;

    pthread_create (&thread2_id, NULL, &char_print, &thread2_args);

    /* Make sure the first thread has finished. */

    pthread_join (thread1_id, NULL);

    /* Make sure the second thread has finished. */

    pthread_join (thread2_id, NULL);

    /* Now we can safely return. */

    return 0;

}

1.3、线程返回值

用线程计算素数

#include <pthread.h>

#include <stdio.h>

/* Compute successive prime numbers (very inefficiently). Return the

Nth prime number, where N is the value pointed to by *ARG. */

void* compute_prime (void* arg)

{

    int candidate = 2;

    int n = *((int*) arg);

    while (1) {

        int factor;

        int is_prime = 1;

        /* Test primality by successive division. */

        for (factor = 2; factor < candidate; ++factor)

            if (candidate % factor == 0) {

                is_prime = 0;

                break;

            }

        /* Is this the prime number we’re looking for? */

        if (is_prime) {

            if (--n == 0)

            /* Return the desired prime number as the thread return value. */

                return (void*) candidate;

        }

        ++candidate;

    }

    return NULL;

}

int main ()

{

    pthread_t thread;

    int which_prime = 5000;

    int prime;

    /* Start the computing thread, up to the 5,000th prime number. */

    pthread_create (&thread, NULL, &compute_prime, &which_prime);

    /* Do some other work here... */

    /* Wait for the prime number thread to complete, and get the result. */

    pthread_join (thread, (void*) &prime);

    /* Print the largest prime it computed. */

    printf(“The %dth prime number is %d.\n”, which_prime, prime);

    return 0;

1.4、线程属性

  • 使用线程属性:

    • 创建pthread_attr_t对象。

    • 调用pthread_attr_init来初始化为默认属性。

    • 可以修改线程属性对象,设置需要的属性值。

    • 调用pthread_create的时候将属性对象指针传入。

    • 调用pthread_attr_destroy来释放属性对象。

    • pthread_attr_t本身不能自动回收,但是可以用pthread_attr_init函数重新初始化线程对象。

  • 使用detach state属性

    • 一个线程可以默认状态下被创建称为可join的线程,也可以创建为detached线程。

    • 一个可join的线程在结束后不会自动被回收,而是要其他线程调用pthread_join来获得此线程的返回值。

    • 一个detached线程在结束后会自动被回收。

    • 用函数pthread_attr_setdetachstate来设定线程属性对象的detach state.

 

#include <pthread.h>

void* thread_function (void* thread_arg)

{

/* Do work here... */

}

int main ()

{

    pthread_attr_t attr;

    pthread_t thread;

    pthread_attr_init (&attr);

    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);

    pthread_create (&thread, &attr, &thread_function, NULL);

    pthread_attr_destroy (&attr);

    /* Do work here... */

    /* No need to join the second thread. */

    return 0;

}

 

2、取消线程

  • 一个线程可以调用pthread_cancel来取消另一个线程。

  • 被取消的线程需要被join来释放资源。

  • 被取消的线程的返回值为PTHREAD_CANCELED

  • 有关线程的取消,一个线程可以为如下三个状态:

    • 可异步取消:一个线程可以在任何时刻被取消。

    • 可同步取消:取消的请求被放在队列中,直到线程到达某个点,才被取消。

    • 不可取消:取消的请求被忽略。

    • 默认状态下,线程是可同步取消的。

  • 调用pthread_setcanceltype来设定线程取消的方式:

    • pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    • pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, NULL);

    • pthread_setcanceltype (PTHREAD_CANCEL_DISABLE, NULL);

 

3、线程私有数据

  • 每个线程私有数据都有一个key

  • 每个线程都使用此key来访问属于它的那份数据。

  • 调用pthread_key_create来为此线程创建一个新的key和数据。

    • 第一个参数是pthread_key_t

    • 第二个参数是一个回收函数,会在线程退出的时候被调用。

  • 每个线程可以调用pthread_setspecific,根据key来设定线程私有数据的值。

  • 调用pthread_getspecific来得到一个线程私有数据。

 

#include <malloc.h>

#include <pthread.h>

#include <stdio.h>

/* The key used to associate a log file pointer with each thread. */

static pthread_key_t thread_log_key;

/* Write MESSAGE to the log file for the current thread. */

void write_to_thread_log (const char* message)

{

    FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);

    fprintf (thread_log, “%s\n”, message);

}

/* Close the log file pointer THREAD_LOG. */

void close_thread_log (void* thread_log)

{

    fclose ((FILE*) thread_log);

}

void* thread_function (void* args)

{

    char thread_log_filename[20];

    FILE* thread_log;

    /* Generate the filename for this thread’s log file. */

    sprintf (thread_log_filename, “thread%d.log”, (int) pthread_self ());

    /* Open the log file. */

    thread_log = fopen (thread_log_filename, “w”);

    /* Store the file pointer in thread-specific data under thread_log_key. */

    pthread_setspecific (thread_log_key, thread_log);

    write_to_thread_log (“Thread starting.”);

    /* Do work here... */

    return NULL;

}

int main ()

{

    int i;

    pthread_t threads[5];

    /* Create a key to associate thread log file pointers in thread-specific data. Use close_thread_log to clean up the file

pointers. */

    pthread_key_create (&thread_log_key, close_thread_log);

    /* Create threads to do the work. */

    for (i = 0; i < 5; ++i)

        pthread_create (&(threads[i]), NULL, thread_function, NULL);

        /* Wait for all threads to finish. */

        for (i = 0; i < 5; ++i)

            pthread_join (threads[i], NULL);

    return 0;

}

 

3.1、线程回收

Linux可以提供回收器(cleanup handler),它是一个函数,在线程退出的时候被调用。

调用pthread_cleanup_push可以注册一个回收器。

调用pthread_cleanup_pop可以注销一个回收器。

pthread_cleanup_pop(0)仅仅注销一个回收器。

pthread_cleanup_pop(1)不仅仅注销这个回收器,而且调用它。

#include <malloc.h>

#include <pthread.h>

/* Allocate a temporary buffer. */

void* allocate_buffer (size_t size)

{

    return malloc (size);

}

/* Deallocate a temporary buffer. */

void deallocate_buffer (void* buffer)

{

    free (buffer);

}

void do_some_work ()

{

    /* Allocate a temporary buffer. */

    void* temp_buffer = allocate_buffer (1024);

    /* Register a cleanup handler for this buffer, to deallocate it in case the thread exits or is cancelled. */

    pthread_cleanup_push (deallocate_buffer, temp_buffer);

    /* Do some work here that might call pthread_exit or might be cancelled... */

    /* Unregister the cleanup handler. Because we pass a nonzero value, this actually performs the cleanup by calling

deallocate_buffer. */

    pthread_cleanup_pop (1);

}

 

4、线程同步及临界区

4.1、互斥锁(Mutexes)

  • Mutex全称MUTual EXclusion lock,也即互斥锁。

  • 同一时刻,只有一个线程可以访问互斥锁。

  • 调用pthread_mutex_init可以创建互斥锁:

    • 第一个参数是pthread_mutex_t

    • 第二个参数是互斥锁属性对象,设为NULL表示使用默认属性。

pthread_mutex_t mutex;

pthread_mutex_init (&mutex, NULL);

  • 创建互斥锁的第二种方式:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  • 调用pthread_mutex_lock来锁定互斥锁。

  • 调用pthread_mutex_unlock来解锁互斥锁。

 

#include <stdio.h>

#include <stdlib.h>

#include <malloc.h>

#include <pthread.h>

struct job

{

    struct job* next;

    int value;

};

struct job* job_queue;

void process_job(struct job*);

pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;

void * process_queue_function (void * arg)

{

    while(1)

    {

        struct job* next_job;

        pthread_mutex_lock(&job_queue_mutex);

        if(job_queue == NULL)

            next_job = NULL;

        else

        {

            printf("begin removing a job...\n");

            next_job = job_queue;

            job_queue = job_queue->next;

            printf("after removing a job...\n");

         }

         pthread_mutex_unlock(&job_queue_mutex);

         if(next_job == NULL)

        {

             sleep(5);

             continue;

        }

        process_job(next_job);

        free(next_job);

    }

    return NULL;

}

void process_job(struct job* p)

{

    printf("The value is : %d.\n", p->value);

}

void enqueue_job(struct job* new_job)

{

    pthread_mutex_lock(&job_queue_mutex);

    printf("begin inserting a job...\n");

    new_job->next = job_queue;

    job_queue = new_job;

    printf("after inserting a job...\n");

    pthread_mutex_unlock(&job_queue_mutex);

}

void * insert_queue_function(void * arg)

{

    int i = 0;

    while(i < 20)

    {

        sleep(1);

        printf("put the value: %d.\n", i);

        struct job* new_job = (struct job*)malloc(sizeof(struct job));

        new_job->next = NULL;

        new_job->value = i;

        enqueue_job(new_job);

        i++;

    }

}

int main()

{

    pthread_t insert_thread, process_thread;

    pthread_create(&insert_thread, NULL, &insert_queue_function, NULL);

    pthread_create(&process_thread, NULL, &process_queue_function, NULL);

    pthread_join(insert_thread, NULL);

    pthread_join(process_thread, NULL);

}

  • 互斥锁有三种:

    • 默认是快锁(fast mutex):两次锁定互斥锁会造成死锁。

    • 递归锁(recursive mutex):允许多次锁定互斥锁,同样次数的解锁可以解除互斥锁。

    • 检错锁(error checking mutex):第二次锁定互斥锁将返回错误码EDEADLK

  • 使用互斥锁属性对象可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值