什么是线程
线程通常叫做轻型级进程。线程是在共享内存空间中并发执行的多道执行路径,他们共享一个进程的资源。
创建进程、线程是有区别的。新进程运行时间独立,执行时几乎独立于创建它的进程;而新线程拥有自己的堆栈、代码,但却与创建者共享全局变量等。
进程与线程的关系
线程的优缺点
- 优点
- 创建一个新线程的代价要比创建一个新进程小得多。
- 让一个程序同时做两件事情是很有用的,即提高效率,又降低成本
- 缺点
- 多线程程序编写中,由于时间偏差、共享了不该共享的变量而造成不良影响的可能性很大
- 将运算通过两个线程的程序在一台单处理器上运行不见得很快。
线程分类
- 用户级线程
主要解决的是上下文切换的问题,其调度算法和调度过程全部有用户决定。 - 内核级线程
有内核调度机制实现。 - 两者关系
现在大多数操作系统都采用用户级线程和内核级线程并存的方法。用户级线程可与内核级线程实现“一对一”,“一对多”的对应关系。
线程的实现
线程实现—创建
- 头文件:#include <pthread.h>
- 原型
int pthread_create(pthread_t *thread,
thread_attr_t *attr,
void *(*start_routine)(void *),
void *arg)
thread:线程标识符
attr:线程属性设置,通常NULL
start_routine:线程函数起始地址
arg:传递给start_routine的参数
线程实现—退出
- 头文件:#include <pthread.h>
- void pthread_exit( void *retval )
retval: pthread_exit调用者线程的返回值,可由其他函数和pthread_join来检测获取。
线程实现—等待
- 头文件:#include <pthread.h>
- pthread_join( pthread_t *th,
void **thread_return )
th:等待线程的标识符
thread_return:用户定义指针,用来存储被等待线程的返回值——*retval
第一个线程例子
#include <stdio.h>
#include <unistd.h>
#include <stdlib.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 != 0) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish...\n");
res = pthread_join(a_thread,&thread_result);
if (res != 0) {
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(3);
strcpy(message, "Bye!");
pthread_exit("Thank you for the CPU time");
}
线程的属性
线程与私有数据
创建线程私有数据
int pthread_key_create(pthread_key_t *key,void (*destr_function)(void *))
注销线程私有数据
- int pthread_key_delete(pthread_key_t key)
读写线程私有数据 - int pthread_setspecific(pthread_key_t key,const void *pointer)
- void *pthread_getspecific(pthread_key_t key)
举例 - 两个线程数据共享(pthread_glob_test.c)
- 两个线程的私有数据(pthread_key_test.c)
属性创建
基本步骤
- ret = pthread_attr_init(&attr);
- ret = pthread_attr_get***(&attr,size);ret = pthread_attr_get*(&attr,**size);
- ret = pthread_attr_set***(&attr,线程属性); ret = pthread_attr_set***(&attr,线程属性);
- ret = pthread_create(&thread,&attr,…);
- ret = pthread_attr_destroy(&attr);
属性描述
线程ID
- 不同进程中创建的线程可能出现ID值相同的情况,这就决定了线程争用范围是在同一进程内竞争。
举例:pthread_create_id.c
detachstate
- 线程处于分离状态还是可连接状态。
举例:pthread_attr_example.c
guardsize
- 线程守护区大小。
schedparam
- 线程调度策略相关参数。
schedpolicy
- 线程调度策略
inheritsched
- 线程调度策略及参数是从创建线程获得还是从属性对象获得。
stackaddr
- 线程堆栈基址。
stacksize
- 线程堆栈大小。
contentionscope
- 线程争用范围。
processor
- 线程绑定的特定处理器。
线程的同步
互斥量
mutex互斥锁
- mutex是一种简单的加锁的方法来控制对共享资源的访问。
- 在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。
- 若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。
互斥锁的操作步骤
主要包括以下几个:
- 互斥锁初始化:pthread_mutex_init
- 互斥锁上锁:pthread_mutex_lock
- 互斥锁解锁:pthread_mutex_unlock
- 消除互斥锁:pthread_mutex_destroy
互斥锁初始化
- #include <pthread.h>
- int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex_attr_t *mutexattr)
mutex:互斥锁
mutexattr:通常设置为NULL
PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
PTHREAD_REEORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁
互斥锁操作
- 头文件
#include <pthread.h> - 函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_destroy(pthread_mutex_t *mutex) - 说明
mutex:互斥锁 - 返回值:成功0,错误-1。
- 举例
mutex_example.c
条件变量
出现原因
初始化条件变量
extern int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr)
销毁条件变量
extern int pthread_cond_destroy(pthread_cond_t *cond)
初始化条件变量举例
pthread_cond_t cv;
Pthread_condattr_t cattr;
int ret;
ret = pthread_cond_init(&cv,NULL);
ret = pthread_cond_init(&cv,&cattr);
通知条件变量
extern int pthread_cond_signal(pthread_cond_t *cond)
extern int pthread_cond_broadcast(pthread_cond_t *cond)
等待条件变量
Extern int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
举例
pthread_cond_example.c
thread_cond.c
读写锁
基本原则
如果某线程申请了读锁,其他线程可以再申请读锁,但不能申请写锁;
如果某线程申请了写锁,其他线程不能申请读锁,也不能申请写锁。
初始化读写锁
extern int pthread_rwlock_init(pthread_rwlock_t *rwlock,pthread_rwlockattr_t *attr)
注销读写锁
extern int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
申请写锁
extern int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
申请读锁
extern int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
解锁
extern int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
举例
pthread_rwlock_example.c
线程与信号
线程在信号操作时有以下特性
- 每一个线程可以向别的线程发送信号。pthread_kill()函数用来完成这一操作。
- 每一个线程可以设置自己的信号阻塞集合。pthread_sigmask()函数用来完成这一操作,其类似于进程的sigprocmask()函数。
- 每个线程可以设置针对某信号处理的方式,但同一进程中对某信号的处理方式只能有一个有效,即最后一次设置的处理方式。
- 如果别的进程向当前进程中发送一个信号,由哪个线程处理是未知的。
线程信号管理函数——发送信号
extern int pthread_kill(pthread_t threadId,
int signo)
theadId:接收信号的线程
signo:信号,=0,进行错误检查而不发送信号
返回值:成功为0;错误-1