一、什么是线程

  在一个程序中的多个执行路线就叫做线程。更准确的定义是:线程是一个进程内部的一个控制序列。所有的进程都至少有一个线程。当进程执行fork调用时,将创建出该进程的一份新副本,这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。

  一个混杂着输入、计算和输出的应用程序,可以将这几个部分分离为3个线程来执行,从而改善程序执行的性能。当输入或输出线程等待连接时,另外一个线程可以继续执行。因此,如果一个进程在任一时刻只能做一件事情的话,线程可以让它在等待连接之类的事情的同时做一些其他有用的事情。一般而言,线程之间的切换需要操作系统做的工作比进程之间的切换少得多,因此多个线程对资源的需求要远小于多个进程。
  线程也有下面一些缺点:①在多线程程序中,因时序上的细微偏差或无意造成的变量共享而引发错误的可能性是很大。②对多线程程序的调试要比对单线程程序的调试困难得多,因为线程之间的交互非常难于控制。③将大量计算分成两个部分,并把这两个部分作为两个不同的线程来运行的程序在一台单处理器机器上并不一定运行得更快,除非计算确实允许它的不同部分可以被同时计算,而且运行它的机器拥有多个处理器核来支持真正的多处理。

二、第一个线程程序

  我们首先来看一个用于管理线程的新函数pthread_create,它的作用是创建一个新进程,类似于创建新进程的fork函数,它的定义如下:

#include<pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

  第一个参数是指向pthread_t类型数据的指针,线程被创建时,这个指针指向的变量中奖被写入一个标识符,我们用该标识符来引用新线程。下一个参数用于设置线程的属性。我们一般不需要特殊的属性,所以只需设置该参数为NULL。最后两个参数分别告诉线程将要启动执行的函数和传递给该函数的参数。

void *(*start_routine)(void *)

  上面一行告诉我们必须要传递一个函数地址,该函数一个指向void的指针为参数,返回的也是一个指向void的指针。因此,可以传递一个任一类型的参数并返回一个任一类型的指针。用fork调用后,父子进程将在同一位置继续执行下去,只是fork调用的返回值是不同的,但对新线程来说,我们必须明确地提供给它一个函数指针,新进程将在这个新位置开始执行。

  线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。注意,绝不能用它来返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不再存在了,这将引起严重的程序漏洞。该函数的定义如下:

#include<pthread.h>
void pthread_exit(void *retval);

  pthread_join函数在线程中的作用等价于进程中用来收集子进程信息的wait函数,定义如下:

#include<pthread.h>
int pthread_join(pthread_t th,void **thread_return);

  第一个参数指定了将要等待的线程,线程通过pthread_create返回的标识符来指定。第二个参数是一个指针,它指向另一个指针,而后者指向线程的返回值。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h> void *thread_function(void *arg);
char message[] = "Hello World";
int main() {
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_create(&a_thread, NULL, thread_function, (void *)message);
if (res != ) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result);
if (res != ) {
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined, it returned %s\n", (char *)thread_result);
printf("Message is now %s\n", message);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg) {
printf("thread_function is running. Argument was %s\n", (char *)arg);
sleep();
strcpy(message, "Bye!");
pthread_exit("Thank you for the CPU time");
}

Linux下用gcc编译为gcc -o pthread1 pthread1.c -lpthread 运行得到的结果为:

wanh@wanh-VirtualBox:~/linux_c_driver/Demo$ ./pthread1
Waiting for thread to finish...
thread_function is running. Argument was Hello World
Thread joined, it returned Thank you for the CPU time
Message is now Bye!

三、同步

1、信号量进行同步

  有两组接口函数用于信号量。一组取自POSIX的实时扩展,用于线程;另一组被称为系统V信号量,常用于进程的同步。信号量是一个特殊类型的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。这意味着如果一个程序中有两个(或更多)的线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

  信号量函数的名字都以sem_开头,线程中使用的基本信号量有4个。信号量通过sem_init函数创建,它的定义如下:

#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);

  这个函数初始化由sem指向的信号量对象,设置它的共享选项,并给它一个初始的整数值。pshared参数控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享。

#include<semaphore.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);

  这两个函数都以一个指针为参数,该指针指向的对象是由sem_init调用初始化的信号量。

  sem_post函数的作用是以原子操作的方式给信号量的值加1,所谓原子操作是指,如果两个线程企图同时给一个信号量加1,它们之间不会互相干扰,而不像如果两个程序同时对同一个文件进行读取、增加、写入操作时可能会引起冲突。

  sem_wait函数以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。因此,如果对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有其他线程增加了该信号量的值使其不再是0为止。

  最后一个信号量函数是sem_destroy,这个函数的作用是,用完信号量后对它进行清理,它的定义如下:

#include<semaphore.h>
int sem_destroy(sem_t *sem);

  例子程序pthread3.c如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.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 res;
pthread_t a_thread;
void *thread_result; res = sem_init(&bin_sem, , );
if (res != ) {
perror("Semaphore initialization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread, NULL, thread_function, NULL);
if (res != ) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Input some text. Enter 'end' to finish\n");
while(strncmp("end", work_area, ) != ) {
fgets(work_area, WORK_SIZE, stdin);
sem_post(&bin_sem);
}
printf("\nWaiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result);
if (res != ) {
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
sem_destroy(&bin_sem);
exit(EXIT_SUCCESS);
} void *thread_function(void *arg) {
sem_wait(&bin_sem);
while(strncmp("end", work_area, ) != ) {
printf("You input %d characters\n", strlen(work_area) -);
sem_wait(&bin_sem);
}
pthread_exit(NULL);
}

gcc编译执行后如下所示:

wanh@wanh-VirtualBox:~/linux_c_driver/Demo$ ./pthread3
Input some text. Enter 'end' to finish
addfgg
You input characters
diengg
You input characters
sddee
You input characters
end Waiting for thread to finish...
Thread joined

  在主线程中,我们等待直到有文本输入,然后调用sem_post增加信号量的值,这将立刻令另一个线程从sem_wait的等待中返回并开始执行。在统计完字符个数之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值为止。而这很容易导致一些错误,我们修改上面程序,把main函数中的读数据循环修改为:

    printf("Input some text. Enter 'end' to finish\n");
while(strncmp("end", work_area, ) != ) {
if (strncmp(work_area, "FAST", ) == ) {
sem_post(&bin_sem);
strcpy(work_area, "Wheeee...");
} else {
fgets(work_area, WORK_SIZE, stdin);
}
sem_post(&bin_sem);
}

  现在,如果输入FAST,程序就会调用sem_post使字符统计线程开始运行,同时立刻用其它数据更新work_area数组。程序运行情况如下所示:

wanh@wanh-VirtualBox:~/linux_c_driver/Demo$ ./pthread3a
Input some text. Enter 'end' to finish
ecdedff
You input characters
ddaaa
You input characters
FAST
You input characters
You input characters
You input characters
end Waiting for thread to finish...
Thread joined

  问题在于,我们的程序依赖其接收文本输入的时间要足够长,这样另一个线程才有时间在主线程还未准备好给它更多的单词去统计之前统计出工作区中字符的个数。当我们试图连续快速地给它两组不同的单词去统计时(键盘输入的FAST和程序自动提供地Wheeee...),第二个线程就没有时间去执行。但信号量已被增加了不止一次,所以字符统计线程就会反复统计字符数目并减少信号量的值,直到它再次变为0为止。

2、用互斥量进行同步

  另一种用在多线程程序中的同步访问方法是使用互斥量。它允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁它。用于互斥量的基本函数和信号量的函数非常相似,它们的定义如下所以:

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *murexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

  与信号量类似,这些函数的参数都是一个先前声明过的对象的指针。对互斥量来说,这个对象的类型为pthread_mutex_t。pthread_mutex_init函数中的属性参数允许我们设置互斥量的属性,而属性控制着互斥量的行为。属性类型默认为fast,但它有一个小缺点:如果程序试图对一个已经加了锁的互斥量调用pthread_mutex_lock,程序救会被阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以互斥量就永远也不会被解锁了,程序也就进入死锁状态。这个问题可以通过改变互斥量的属性来解决,我们可以让它检查这种情况并返回一个错误,或者让它递归的操作,给同一个线程加上多个锁,但必须注意在后面执行同等数量的解锁操作。

四、取消一个线程

  有时,我们想让一个线程可以要求另一线程终止,就像给它发送一个信号一样,线程有方法可以做到这一点,与信号处理一样,线程可以在被要求终止时改变其行为。

#include<pthread.h>
int pthread_cancel(pthread_t thread);

  这个函数提供一个线程标识符,我们就可以发送请求来取消它,但在接收到取消请求的一端,事情会稍微复杂一点。线程可以用pthread_setcancelstate设置自己的取消状态。

#include<pthread.h>
int pthread_setcancelstate(int state,int *oldstate);

  第一个参数的取值可以是PTHREAD_CANCEL_ENABLE,这个值允许线程接收取消请求;或者是PTHREAD_CANCEL_DISABLE,它的作用是忽略取消请求。oldstate指针用于获取先前的取消状态。如果取消请求被接受了,线程就可以进入第二个控制层次,用pthread_setcanceltype设置取消类型。

#include<pthread.h>
int pthread_setcanceltype(int type,int *oldtype);

  type参数可以有两种取值:一个是PTHREAD_CANCEL_ASYNCHRONOUS,它将使得在接受到取消请求后立即采取行动;另外一个是PTHREAD_CANCEL_DEFERRED,它将使得在接受到取消请求后,一直等待直到线程执行了下述函数之一后才采取行动。具体是函数pthread_join、pthread_cond_wait、pthread_cond_timewait、pthread_testcancel、sem_wait或sigwait。

  oldtype参数可以保存先前的状态,如果不想知道先前的状态,可以传递NULL给它。默认情况下,线程在启动时的取消状态为PTHREAD_CANCEL_ENABLE,取消类型是PTHREAD_CANCEL_DEFERRED。

POSIX线程学习的更多相关文章

  1. POSIX线程接口编程学习心得

    由于实验需要,需要了解下C语言多线程编程的知识,于是学习了下POSIX线程编程的知识,有点心得,记录并分享一下. POSIX(可移植操作系统接口)线程是提高代码响应和性能的有力手段.与标准 fork( ...

  2. posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序

    posix 线程(一):线程模型.pthread 系列函数 和 简单多线程服务器端程序 一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属 ...

  3. 通用线程:POSIX 线程详解,第 3 部分

    通用线程:POSIX 线程详解,第 3 部分 使用条件变量提高效率 Daniel Robbins, 总裁兼 CEO, Gentoo Technologies, Inc. 简介: 本文是 POSIX 线 ...

  4. POSIX 线程详解(经典必看)

    http://www.cnblogs.com/sunminmin/p/4479952.html 总共三部分: 第一部分:POSIX 线程详解                               ...

  5. 在 POSIX 线程编程中避免内存泄漏

    检测和避免 POSIX 线程内存泄漏的技巧 POSIX 线程(pthread)编程定义了一套标准的 C 编程语言类型.函数和常量 — 且 pthreads 提供了一种强大的线程管理工具.要充分使用 p ...

  6. posix线程库1

    posix线程库重要的程度不言而喻,这些天学习这个,参考 https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/   首先先 ...

  7. linux网络编程之posix线程(二)

    继续接着上次的posix线程来学习: 回顾一下创建线程的函数: pthread_att_t属性变量是需要进行初始化才能够用的,一定初始化了属性变量,它就包含了线程的多种属性的值,那到底有哪些属性了,下 ...

  8. Linux线程学习(一)

    一.Linux进程与线程概述 进程与线程 为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间.不同的线程可以存取内存中的同一个变量.所以,程序中的所有线程都可 ...

  9. Linux线程学习(二)

    线程基础 进程 系统中程序执行和资源分配的基本单位 每个进程有自己的数据段.代码段和堆栈段 在进行切换时需要有比较复杂的上下文切换   线程 减少处理机的空转时间,支持多处理器以及减少上下文切换开销, ...

随机推荐

  1. Linux Centos6.5 升级默认Python2.6.6到Python2.7.13

    以下例子基于python 2.7.9,其他版本同理.大致的命令都是差不多的,安装完成之后,输入Python --vertion ,看到系统默认的版本已经替换为2.7版本了 1.下载python wge ...

  2. laravel 安装添加多站点

    官方文档如下 https://learnku.com/laravel/t/1160/laravel-nginx-multi-site-configuration

  3. MySQL数据库的原理

    点进去就是你历经千辛万苦找到的数据库的原理: https://www.cnblogs.com/smallyard/p/5626061.html

  4. Struts2+Datagrid表格显示(可显示多表内容)

    概述 最近学到EasyUI的Datagrid数据网格,然后就做了一个小例子,中间层利用Struts2来完成,DAO层用的是Hibernate. 数据库 数据库涉及到stuednt(name,noid, ...

  5. Python的scrapy之爬取链家网房价信息并保存到本地

    因为有在北京租房的打算,于是上网浏览了一下链家网站的房价,想将他们爬取下来,并保存到本地. 先看链家网的源码..房价信息 都保存在 ul 下的li 里面 ​ 爬虫结构: ​ 其中封装了一个数据库处理模 ...

  6. Python学习:11.Python装饰器讲解(二)

    回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...

  7. Java——final代码块是否一定被执行---18.11.08

    辨析:final语句块一定会被执行吗? 答案是 不一定!!! 至少有两种情况下finally语句是不会被执行的: (1)try语句没有被执行到,如在try语句之前return就返回了,这样final语 ...

  8. Android Dagger2.0 学习一下

    0.前言 个人感觉通过项目学习一些牛逼的框架,效果挺不错的. 1.个人理解 一直觉得Dagger2比较高大上,网上看了很多资料,很多,没有感觉. 然后怀疑智商问题,然后放弃了. 最后因为要做一个项目, ...

  9. 成都Uber优步司机奖励政策(2月19日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  10. 成都Uber优步司机奖励政策(1月21日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...