前面在进程里也说过,父子进程共用一套程序主体(CPU执行的机器指令),不共用数据段、堆、栈(不考虑写时复制)。
线程在创建之后,就与其他线程共享所在进程的所有信息,包括可执行程序代码、程序的全局内存和堆内存、栈、文件描述符
创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread
- thread,新创建的线程 ID 会被设置成 thread 指向的内存单元。
- attr,定制线程属性,指向 pthread_attr_t 结构体(由 pthread_attr_init 初始化)
11-4-pthread.c
#include "apue.h"
#include <pthread.h>
pthread_t ntid;
void
printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %lu tid %lu (0x%lx) \n",
s, (unsigned long)pid, (unsigned long)tid,(unsigned long)tid);
}
void *
thr_fn(void *arg)
{
printids("new thread: ");
return ((void *)0);
}
int main(void)
{
int err;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0) {
err_exit(err, "can't create thread");
}
printids("main thread:");
sleep(1);
exit(0);
}
编译:
$ gcc 11-4-pthread.c -g -O2 -o thread.out -lapue -lpthread
执行:
$ ./thread.out
main thread: pid 8272 tid 139793605408576 (0x7f243c313740)
new thread: pid 8272 tid 139793596901120 (0x7f243baf6700)
上面的流程如下:
- 3过程调用
pthread_creade
创建线程之后,新的线程从thr_fn
开始执行
共享资源
同一进程内的所有线程共用堆、栈会造成竞争,读操作需要一个存储器周期,写操作需要两个存储器周期。
互斥量
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),访问结束后释放互斥量(解锁)
动作 | 函数 | 参数 | 描述 | 返回值 |
---|---|---|---|---|
创建 | pthread_mutex_init | pthread_mutex_t *mutex const pthread_mutexattr_t *attr | 创建 mutex | |
加锁 | pthread_mutex_lock | pthread_mutex_t *mutex | 对 mutex 加锁。若 mutex 已锁,则线程阻塞,直到 mutex 可用 | |
pthread_mutex_trylock | pthread_mutex_t *mutex | 无阻塞加锁 | 成功返回0 | |
解锁 | pthread_mutex_unlock | pthread_mutex_t *mutex | 解锁 | 成功返回0 |
删锁 | pthread_mutex_destroy | pthread_mutex_t *mutex | 释放资源 |
一个使用互斥量的示例:
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
/*
* 一个锁对象
* f_count:每当一个线程成功获取一个锁,
* 应对该值+1,用于记录锁被引用的次数
* f_lock:互斥量
* f_id:读、写操作的资源
*/
struct foo {
int f_count;
pthread_mutex_t f_lock;
int f_id;
};
/*
*可能造成冲突的资源
*/
struct foo *ziyuan;
/*
* 初始化一个锁对象
* - 为锁对象分配内存
* - f_count = 1
*/
struct foo *
foo_alloc(int id)
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL ) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
return(NULL);
}
}
return(fp);
};
/*
*添加一个到锁对象的引用,f_count + 1
*/
void
foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
/*
*删除一个到锁对象的引用
*/
void
foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0) {
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}
/*
* 线程处理函数
*/
void *
thr_fn(void *arg)
{
//写操作
foo_hold(ziyuan);
ziyuan->f_id = 7;
foo_rele(ziyuan);
//读操作
foo_hold(ziyuan);
printf("child thread: id is %d\n", ziyuan->f_id);
foo_rele(ziyuan);
return ((void *)1);
}
int main(void)
{
int err;
pthread_t ntid;
void *tret;
ziyuan = foo_alloc(5);
//创建线程
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0) {
err_exit(err, "can't create thread");
}
//写操作
foo_hold(ziyuan);
ziyuan->f_id = 6;
foo_rele(ziyuan);
//读操作
foo_hold(ziyuan);
printf("main thread: id is %d\n", ziyuan->f_id);
foo_rele(ziyuan);
//主线程阻塞
err = pthread_join(ntid, &tret);
if (err != 0) {
err_exit(err, "join thread failed");
}
return 0;
}
编译:16
$ gcc 11-6-mutex.c -g -O2 -o 11-6-mutex.out -lapue -lpthread
执行:
$ ./11-6-mutex.out
main thread: id is 6
child thread: id is 7
图示演示的一个简化的foo_hold、foo_rele的流程
死锁
本来想写个简单的程序演示一下,写到一般发现我还没有能力造成那个死锁的点。
设想两个线程A和B,在第一时间线程A已经对资源A加锁成功,线程B对资源B加锁;第二时间,线程A试图锁住资源B,线程B试图锁住资源A。这样就造成了两个线程都阻塞在了加锁这个操作上。
我试图通过下面的例子重现死锁的现象,然而失败了:
#include "apue.h"
#include <pthread.h>
struct foo {
int f_count;
pthread_mutex_t f_lock;
int f_id;
};
/*
* 造成冲突的资源
*/
struct foo *ziyuan1;
struct foo *ziyuan2;
/*
* 初始化一个锁对象
* - 为锁对象分配内存
* - f_count = 1
*/
struct foo *
foo_alloc(int id)
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
return (NULL);
}
}
return fp;
}
/*
* 添加一个到锁对象的引用
*/
void *
foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
/*
*删除一个到锁对象的引用
*/
void *
foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0) {
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}
/*
* 线程处理函数
*/
void *
thr_fn(void *arg)
{
foo_hold(ziyuan2);
printf("thild thread: id is %d\n", ziyuan2->f_id);
sleep(1);
foo_hold(ziyuan1);
printf("thild thread: id is %d\n", ziyuan1->f_id);
foo_rele(ziyuan1);
foo_rele(ziyuan2);
return ((void *)1);
}
int
main(void)
{
int err;
pthread_t ntid;
void *tret;
ziyuan1 = foo_alloc(5);
ziyuan2 = foo_alloc(7);
//创建线程
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0) {
err_exit(err, "can't create thread");
}
//写操作
foo_hold(ziyuan1);
printf("main thread: id is %d\n", ziyuan1->f_id);
sleep(1);
foo_hold(ziyuan2);
printf("main thread: id is %d\n", ziyuan2->f_id);
foo_rele(ziyuan1);
foo_rele(ziyuan2);
//主线程阻塞
err = pthread_join(ntid, &tret);
if (err != 0) {
err_exit(err, "join thread failed");
}
return 0;
}
编译执行后的结果并没有造成程序的一直进行:
$ gcc 11-6-deadlock.c -g -O2 -o 11-6-deadlock.out -lapue -lpthread
$ ./11-6-deadlock.out
main thread: id is 5
thild thread: id is 7
main thread: id is 7
thild thread: id is 5
所以目前我只能从理论上来分析这个问题。
一个解决这个问题的方法,当需要两个及以上资源加锁时,永远按照固定的顺序对资源加锁。
读写锁
读写锁是互斥量的一种优化(具有更高的并发性),读的优化。
读一个资源的时候不会对资源更改,当线程A读该资源时,资源处于读加锁状态。线程B也对该资源读加锁,成功;线程C对该资源写加锁,失败。
当线程C对该资源写加锁时,资源处于写加锁状态。线程A对该资源读加锁,失败;线程D对该资源写加锁,失败。
动作 | 函数 | 参数 | 描述 | 返回值 |
---|---|---|---|---|
创建 | pthread_rwlock_init | pthread_rwlock_t *restrict rwlock pthread_rwlockattr_t *restrict attr | 初始化读写锁,attr为null指针设置默认属性 | 成功返回0 |
加锁-读 | pthread_rwlock_rdlock | pthread_rwlock_t *restrict rwlock | 成功返回0 | |
pthread_rwlock_tryrdlock | ||||
加锁-写 | pthread_rwlock_wrlock | pthread_rwlock_t *restrict rwlock | 成功返回0 | |
pthread_rwlock_tryrdlock | ||||
解锁 | pthread_rwlock_unlock | pthread_rwlock_t *restrict rwlock | 成功返回0 | |
删锁 | pthread_rwlock_destroy | pthread_rwlock_t *restrict rwlock | 释放为读写锁分配的资源 | 成功返回0 |